基于CH549的练习小项目——地铁板
前言

新的CH549练习项目来啦!这次带来的教程项目名是地铁板,如上图所示,他本身具有较高颜值,同时也是一个可以帮你巩固CH549编程的项目。和往常一样,此次依然作为51的进阶练习,基础薄弱的可以看verimake的51相关教程《以 CH549 为例的 51 教程》
地铁板简介
地铁板正面
其实从正面的图便可以看出,本次项目的地铁板的灵感来源就是南京市的地铁路线的简略图,其中包括了许多南京关键的站点,通过每个站点后面的电阻和橙色LED灯所组成的站点灯可以模拟地铁的路线循环。
地铁板背面
在地铁板的背面可以看到9个引脚,分别对应的接口也在图中标识出来了,具体的接线如下表所示:
原理表 | LED_A1 | P3.5 |
| LED_A2 | P2.2 |
| LED_A3 | P2.4 |
| LED_A4 | P2.6 |
| LED_A5 | P2.7 |
| LED_B1 | P1.1 |
| LED_B2 | P1.4 |
| LED_B3 | P1.6 |
| LED_B4 | P1.6 |
由之前的图片可以知道一共有20盏灯,每盏灯分别用一个IO口控制肯定不太现实,所以此处套用了一个矩阵表来定义各个灯,即如下表所示,将Ax置高电压,Bx置低电压,然后对应数列表中的LED灯便会亮,既节省了IO口也方便操作。
原理表 | A1 | A2 | A3 | A4 | A5 |
B1 | 1 | 2 | 3 | 4 | 5 |
B2 | 6 | 7 | 8 | 9 | 10 |
B3 | 11 | 12 | 13 | 14 | 15 |
B4 | 16 | 17 | 18 | 19 | 20 |
程序设计
本次项目的程序较为简单,所以直接将程序封装到了专门的.c文件下了。
CH549_SubwayBoard.h
#ifndef __CH549_IIC_H__
#define __CH549_IIC_H__
#include "CH549_sdcc.h" //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
#include "CH549_DEBUG.h" //CH549官方提供库的头文件,定义了一些关于主频,延时,串口设置,看门口,赋值设置等基础函数
#include "stdlib.h" //调用随机数函数
/****************** 参数定义 ******************/
#define LED_A1 P3_5
#define LED_A2 P2_2
#define LED_A3 P2_4
#define LED_A4 P2_6
#define LED_A5 P2_7
#define LED_B1 P1_1
#define LED_B2 P1_4
#define LED_B3 P1_5
#define LED_B4 P1_6
/****************** 外部调用子函数 ****************************/
extern UINT8 SubwayLine[6][7];
extern UINT8 SubwayCircle[4][9];
extern void LIGHT_ON(UINT8 LIGHT);
extern void LIGHT_OFF(UINT8 LIGHT);
extern void LIGHT_Flash(UINT8 LIGHT);
extern void LIGHT_Clean();
extern void LIGHT_AllOn();
extern void LIGHT_TLineOn();
extern void LIGHT_FLineOn();
extern void LIGHT_Converge(UINT16 FREQs);
extern void LIGHT_Disperse(UINT16 FREQs);
extern void LIGHT_Random(UINT8 FREQs);
#endif
.h的头文件中简单定义了引脚IO口以及之后的.c文件中的函数声明。
CH549_SubwayBoard.c
#include "CH549_SubwayBoard.h"
//地铁线路
UINT8 SubwayLine[6][7] = {
{ 10, 15, 14, 13, 12, 7 , 6 },
{ 5 , 4 , 3 , 8 , 13, 17, 16},
{ 20, 15, 9 , 8 , 2 , 7 , 11},
{ 19, 14, 9 , 5 , 0 , 0 , 0 },
{ 18, 17, 12, 13, 14, 15, 10},
{ 16, 7 , 1 , 0 , 0 , 0 , 0 },
};
UINT8 SubwayCircle[4][9] = {
{ 1 , 5 , 6 , 10, 11, 16, 18, 19, 20},
{ 4 , 7 , 14, 15, 17, 0 , 0 , 0 , 0 },
{ 2 , 3 , 9 , 12, 13, 0 , 0 , 0 , 0 },
{ 8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
};
//亮灯
void LIGHT_ON(unsigned char LIGHT)
{
switch (LIGHT)
{
case 1:LED_A1 = 1;LED_B1 = 0;break;
case 2:LED_A2 = 1;LED_B1 = 0;break;
case 3:LED_A3 = 1;LED_B1 = 0;break;
case 4:LED_A4 = 1;LED_B1 = 0;break;
case 5:LED_A5 = 1;LED_B1 = 0;break;
case 6:LED_A1 = 1;LED_B2 = 0;break;
case 7:LED_A2 = 1;LED_B2 = 0;break;
case 8:LED_A3 = 1;LED_B2 = 0;break;
case 9:LED_A4 = 1;LED_B2 = 0;break;
case 10:LED_A5 = 1;LED_B2 = 0;break;
case 11:LED_A1 = 1;LED_B3 = 0;break;
case 12:LED_A2 = 1;LED_B3 = 0;break;
case 13:LED_A3 = 1;LED_B3 = 0;break;
case 14:LED_A4 = 1;LED_B3 = 0;break;
case 15:LED_A5 = 1;LED_B3 = 0;break;
case 16:LED_A1 = 1;LED_B4 = 0;break;
case 17:LED_A2 = 1;LED_B4 = 0;break;
case 18:LED_A3 = 1;LED_B4 = 0;break;
case 19:LED_A4 = 1;LED_B4 = 0;break;
case 20:LED_A5 = 1;LED_B4 = 0;break;
}
}
//灭灯
void LIGHT_OFF(unsigned char LIGHT)
{
switch (LIGHT)
{
case 1:LED_A1 = 0;LED_B1 = 1;break;
case 2:LED_A2 = 0;LED_B1 = 1;break;
case 3:LED_A3 = 0;LED_B1 = 1;break;
case 4:LED_A4 = 0;LED_B1 = 1;break;
case 5:LED_A5 = 0;LED_B1 = 1;break;
case 6:LED_A1 = 0;LED_B2 = 1;break;
case 7:LED_A2 = 0;LED_B2 = 1;break;
case 8:LED_A3 = 0;LED_B2 = 1;break;
case 9:LED_A4 = 0;LED_B2 = 1;break;
case 10:LED_A5 = 0;LED_B2 = 1;break;
case 11:LED_A1 = 0;LED_B3 = 1;break;
case 12:LED_A2 = 0;LED_B3 = 1;break;
case 13:LED_A3 = 0;LED_B3 = 1;break;
case 14:LED_A4 = 0;LED_B3 = 1;break;
case 15:LED_A5 = 0;LED_B3 = 1;break;
case 16:LED_A1 = 0;LED_B4 = 1;break;
case 17:LED_A2 = 0;LED_B4 = 1;break;
case 18:LED_A3 = 0;LED_B4 = 1;break;
case 19:LED_A4 = 0;LED_B4 = 1;break;
case 20:LED_A5 = 0;LED_B4 = 1;break;
}
}
//灯闪烁
void LIGHT_Flash(UINT8 LIGHT)
{
LIGHT_ON(LIGHT);
mDelayuS(100);
LIGHT_OFF(LIGHT);
}
//清屏
void LIGHT_Clean()
{
for (UINT8 LIGHT = 1; LIGHT < 21; LIGHT++)
{
LIGHT_OFF(LIGHT);
}
}
//灯全开
void LIGHT_AllOn()
{
for (UINT8 LIGHT = 1; LIGHT < 21; LIGHT++)
{
LIGHT_ON(LIGHT);
}
}
//正方向按线路逐渐点亮再熄灭
void LIGHT_TLineOn()
{
UINT8 DOT,LINE;
for ( LINE = 0; LINE < 6; LINE++)
{
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[LINE][DOT])
{
LIGHT_ON(SubwayLine[LINE][DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[LINE][DOT]);
}
}
}
}
//反方向按线路逐渐点亮再熄灭
void LIGHT_FLineOn()
{
UINT8 DOT,LINE;
for ( LINE = 0; LINE < 6; LINE++)
{
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[LINE][6-DOT])
{
LIGHT_ON(SubwayLine[LINE][6-DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[LINE][6-DOT]);
}
}
}
}
//灯聚合
void LIGHT_Converge(UINT16 FREQs)
{
float TIME[4] = {1 ,1.8, 1.8, 9};
UINT8 DOT,CIRCLE;
UINT16 FREQ;
for ( CIRCLE = 0; CIRCLE < 4; CIRCLE++)
{
for ( FREQ = 0; FREQ < FREQs * TIME[CIRCLE]; FREQ++)
{
for ( DOT = 0; DOT < 9; DOT++)
{
if(SubwayCircle[CIRCLE][DOT])
{
LIGHT_Flash(SubwayCircle[CIRCLE][DOT]);
}
}
}
}
}
//灯发散
void LIGHT_Disperse(UINT16 FREQs)
{
float TIME[4] = {1 ,1.8, 1.8, 9};
UINT8 DOT,CIRCLE;
UINT16 FREQ;
for ( CIRCLE = 0; CIRCLE < 4; CIRCLE++)
{
for ( FREQ = 0; FREQ < FREQs * TIME[3 - CIRCLE]; FREQ++)
{
for ( DOT = 0; DOT < 9; DOT++)
{
if(SubwayCircle[3 - CIRCLE][DOT])
{
LIGHT_Flash(SubwayCircle[3 - CIRCLE][DOT]);
}
}
}
}
}
//随机跑灯
void LIGHT_Random(UINT8 FREQs)
{
UINT8 i,m,DOT;
for ( i = 0; i < FREQs; i++)
{
if (rand() % 2)
{
m = rand() % 6;
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[m][DOT])
{
LIGHT_ON(SubwayLine[m][DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[m][DOT]);
}
}
}else
{
m = rand() % 6;
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[m][6 - DOT])
{
LIGHT_ON(SubwayLine[m][6 - DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[m][6 - DOT]);
}
}
}
mDelaymS(150);
}
}
接下来将对程序分部分讲解
基础亮灭灯程序
//亮灯
void LIGHT_ON(unsigned char LIGHT)
{
switch (LIGHT)
{
case 1:LED_A1 = 1;LED_B1 = 0;break;
case 2:LED_A2 = 1;LED_B1 = 0;break;
case 3:LED_A3 = 1;LED_B1 = 0;break;
case 4:LED_A4 = 1;LED_B1 = 0;break;
case 5:LED_A5 = 1;LED_B1 = 0;break;
case 6:LED_A1 = 1;LED_B2 = 0;break;
case 7:LED_A2 = 1;LED_B2 = 0;break;
case 8:LED_A3 = 1;LED_B2 = 0;break;
case 9:LED_A4 = 1;LED_B2 = 0;break;
case 10:LED_A5 = 1;LED_B2 = 0;break;
case 11:LED_A1 = 1;LED_B3 = 0;break;
case 12:LED_A2 = 1;LED_B3 = 0;break;
case 13:LED_A3 = 1;LED_B3 = 0;break;
case 14:LED_A4 = 1;LED_B3 = 0;break;
case 15:LED_A5 = 1;LED_B3 = 0;break;
case 16:LED_A1 = 1;LED_B4 = 0;break;
case 17:LED_A2 = 1;LED_B4 = 0;break;
case 18:LED_A3 = 1;LED_B4 = 0;break;
case 19:LED_A4 = 1;LED_B4 = 0;break;
case 20:LED_A5 = 1;LED_B4 = 0;break;
}
}
//灭灯
void LIGHT_OFF(unsigned char LIGHT)
{
switch (LIGHT)
{
case 1:LED_A1 = 0;LED_B1 = 1;break;
case 2:LED_A2 = 0;LED_B1 = 1;break;
case 3:LED_A3 = 0;LED_B1 = 1;break;
case 4:LED_A4 = 0;LED_B1 = 1;break;
case 5:LED_A5 = 0;LED_B1 = 1;break;
case 6:LED_A1 = 0;LED_B2 = 1;break;
case 7:LED_A2 = 0;LED_B2 = 1;break;
case 8:LED_A3 = 0;LED_B2 = 1;break;
case 9:LED_A4 = 0;LED_B2 = 1;break;
case 10:LED_A5 = 0;LED_B2 = 1;break;
case 11:LED_A1 = 0;LED_B3 = 1;break;
case 12:LED_A2 = 0;LED_B3 = 1;break;
case 13:LED_A3 = 0;LED_B3 = 1;break;
case 14:LED_A4 = 0;LED_B3 = 1;break;
case 15:LED_A5 = 0;LED_B3 = 1;break;
case 16:LED_A1 = 0;LED_B4 = 1;break;
case 17:LED_A2 = 0;LED_B4 = 1;break;
case 18:LED_A3 = 0;LED_B4 = 1;break;
case 19:LED_A4 = 0;LED_B4 = 1;break;
case 20:LED_A5 = 0;LED_B4 = 1;break;
}
}
该部分按照之前所提到的原理矩阵,通过简单的switch判断打开或者关闭对应的灯。
对全部灯的操作
//清屏
void LIGHT_Clean()
{
for (UINT8 LIGHT = 1; LIGHT < 21; LIGHT++)
{
LIGHT_OFF(LIGHT);
}
}
//灯全开
void LIGHT_AllOn()
{
for (UINT8 LIGHT = 1; LIGHT < 21; LIGHT++)
{
LIGHT_ON(LIGHT);
}
}
该部分可以说是作为灯亮或者灭的基础,再亮灯之前最好清一次屏,即把所有灯熄灭;同理如果想从相反的角度思考,灯全是亮的,从中灭灯时,最好先将所有灯都打开。
地铁路线走灯

//地铁线路
UINT8 SubwayLine[6][7] = {
{ 10, 15, 14, 13, 12, 7 , 6 },
{ 5 , 4 , 3 , 8 , 13, 17, 16},
{ 20, 15, 9 , 8 , 2 , 7 , 11},
{ 19, 14, 9 , 5 , 0 , 0 , 0 },
{ 18, 17, 12, 13, 14, 15, 10},
{ 16, 7 , 1 , 0 , 0 , 0 , 0 },
};
//正方向按线路逐渐点亮再熄灭
void LIGHT_TLineOn()
{
UINT8 DOT,LINE;
for ( LINE = 0; LINE < 6; LINE++)
{
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[LINE][DOT])
{
LIGHT_ON(SubwayLine[LINE][DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[LINE][DOT]);
}
}
}
}
//反方向按线路逐渐点亮再熄灭
void LIGHT_FLineOn()
{
UINT8 DOT,LINE;
for ( LINE = 0; LINE < 6; LINE++)
{
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[LINE][6-DOT])
{
LIGHT_ON(SubwayLine[LINE][6-DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[LINE][6-DOT]);
}
}
}
一开始的二阶数组其实就是对应地铁板正面的路线比如说第一列中的{10,15,14,13,12,7,6}即为一号线的路线,对应到下面的两个函数,便可以实现让灯按照地铁路线运行。由于在简略图中每一条线路的大小不尽相同,所以在灯闪烁前加上一段判断,即之前数组中对没有灯亮的部分赋的值为0,由上面的部分可以知道0并不会进入switch判断,这样就可以让灯按路线走的时候不产生停顿。基本的就是两层循环嵌套,第一层是选线路,第二层是线路上的站点灯。
灯聚合灯发散

UINT8 SubwayCircle[4][9] = {
{ 1 , 5 , 6 , 10, 11, 16, 18, 19, 20},
{ 4 , 7 , 14, 15, 17, 0 , 0 , 0 , 0 },
{ 2 , 3 , 9 , 12, 13, 0 , 0 , 0 , 0 },
{ 8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
};
//灯闪烁
void LIGHT_Flash(UINT8 LIGHT)
{
LIGHT_ON(LIGHT);
mDelayuS(100);
LIGHT_OFF(LIGHT);
}
//灯聚合
void LIGHT_Converge(UINT16 FREQs)
{
float TIME[4] = {1 ,1.8, 1.8, 9};
UINT8 DOT,CIRCLE;
UINT16 FREQ;
for ( CIRCLE = 0; CIRCLE < 4; CIRCLE++)
{
for ( FREQ = 0; FREQ < FREQs * TIME[CIRCLE]; FREQ++)
{
for ( DOT = 0; DOT < 9; DOT++)
{
if(SubwayCircle[CIRCLE][DOT])
{
LIGHT_Flash(SubwayCircle[CIRCLE][DOT]);
}
}
}
}
}
//灯发散
void LIGHT_Disperse(UINT16 FREQs)
{
float TIME[4] = {1 ,1.8, 1.8, 9};
UINT8 DOT,CIRCLE;
UINT16 FREQ;
for ( CIRCLE = 0; CIRCLE < 4; CIRCLE++)
{
for ( FREQ = 0; FREQ < FREQs * TIME[3 - CIRCLE]; FREQ++)
{
for ( DOT = 0; DOT < 9; DOT++)
{
if(SubwayCircle[3 - CIRCLE][DOT])
{
LIGHT_Flash(SubwayCircle[3 - CIRCLE][DOT]);
}
}
}
}
}
该部分其实和地铁线路可能不沾边了,这部分想表达出一种所有线路都由地铁板中心的那一站向周围发散或者是由哥哥末尾的站向中心聚合。
到了这里其实就稍微有点难度了,也许你也会说,重新建立新的数组,按组亮就行了,想是这么想,但是实现不了,由于我们之前将20盏灯弄成了4×5的矩阵,虽然确实省了不少事,但是也埋下了颗问题的种子,简而言之就是,假设你选择让1号灯和7号灯一起亮,通过函数将A1和A2置高电平,B1和B2置低电平,聪明的你会发现这里实际上是亮了4盏灯,1、2、6、7都亮了,这就是其中的冲突所在,通过之前设定的亮灯程序无法让他仅仅同时亮1和7。
这里就要用到一种新的思维方式了,即早期Flash动画的思维,当一帧一帧的动作图片以较快的速度切换的时候,眼睛实际上跟不上他切换的速度的,在眼睛看来那就是连贯的,举个简单的例子就是那种老式的计算器,如下图所示:
其他每个数字都是7段数码管,数字的每一笔都是一个IO控制的,但是都是同一个IO口,靠其他IO控制每一位,一次显示一位,以较快速度循环,在我们眼睛看来他们就是同时显示的,这也是为什么有些计算器出问题之后电子屏上的数字会像流水灯一样循环。
这里其实从这段程序中也可以看出,同样是应用了这种方法,这样就可以让本来冲突的灯在我们看起来是同时亮的。这里用了三层嵌套,第一层是将站点分为4圈,第二层是通过循环次数让每一圈显示的持续时间,第三层就是每一圈中间的各个站点。
随机走灯
//地铁线路
UINT8 SubwayLine[6][7] = {
{ 10, 15, 14, 13, 12, 7 , 6 },
{ 5 , 4 , 3 , 8 , 13, 17, 16},
{ 20, 15, 9 , 8 , 2 , 7 , 11},
{ 19, 14, 9 , 5 , 0 , 0 , 0 },
{ 18, 17, 12, 13, 14, 15, 10},
{ 16, 7 , 1 , 0 , 0 , 0 , 0 },
};
//随机跑灯
void LIGHT_Random(UINT8 FREQs)
{
UINT8 i,m,DOT;
for ( i = 0; i < FREQs; i++)
{
if (rand() % 2)
{
m = rand() % 6;
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[m][DOT])
{
LIGHT_ON(SubwayLine[m][DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[m][DOT]);
}
}
}else
{
m = rand() % 6;
for ( DOT = 0; DOT < 7; DOT++)
{
if (SubwayLine[m][6 - DOT])
{
LIGHT_ON(SubwayLine[m][6 - DOT]);
mDelaymS(50);
LIGHT_OFF(SubwayLine[m][6 - DOT]);
}
}
}
mDelaymS(150);
}
}
这里的程序其实沿用之前跑灯的程序,仅在其中加入了随机数,先判断是正方向还是反方向,然后判断走那一条线路。
main.c
#include <CH549_sdcc.H> //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
#include <CH549_DEBUG.h> //CH549官方提供库的头文件,定义了一些关于主频,延时,串口设置,看门口,赋值设置等基础函数
#include <CH549_SubwayBoard.h> //地铁板特供CH549头文件
// #include <stdlib.h> //调用随机数函数
/********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
void main(void)
{
UINT8 i;
LIGHT_Clean();
/* 主循环 */
while (1)
{
LIGHT_Converge(40);
LIGHT_Converge(40);
LIGHT_Converge(40);
mDelaymS(200);
LIGHT_TLineOn();
mDelaymS(200);
LIGHT_Disperse(40);
LIGHT_Disperse(40);
LIGHT_Disperse(40);
mDelaymS(200);
LIGHT_FLineOn();
mDelaymS(200);
LIGHT_AllOn();
mDelaymS(50);
LIGHT_Clean();
mDelaymS(50);
LIGHT_AllOn();
mDelaymS(50);
LIGHT_Clean();
mDelaymS(50);
LIGHT_AllOn();
mDelaymS(50);
LIGHT_Clean();
mDelaymS(50);
LIGHT_Random(5);
mDelaymS(200);
for ( i = 0; i < 6; i++)
{
LIGHT_Converge(10 * (6 - i));
}
for ( i = 0; i < 6; i++)
{
LIGHT_Disperse(10 * (6 - i));
}
LIGHT_AllOn();
mDelaymS(1000);
}
}
主函数实际上就是将之前封装的函数交叉套用。
后话
至此地铁板的解说就到此结束了,具体的gif就不做展示了(时间长,文件大,上传不了),有兴趣的同学可以自己试试。虽然本身较为简单,但是对于初学者来说,也是一次练习的好机会,玩的同时还能学到东西,特别是其中巧妙跳过原理限制的思维也许会对之后的学习有所帮助。这次的程序我将其分为两种,一种是本身封装好的一切,同时按照我的想法拼装成顺序的演示,成品版;另一种是仅包含基本封装的函数的,让同学们发挥想象自我发挥进行练习学习的,基础版。这两种我均上传至gitee仓库上文件名为:
欢迎大家参考!