0.实训平台布局
我们板子上一共有这些,现在从入门开始整理
现在开始一个模块一个模块的整理,从入门开始。
初出茅庐·LED灯模块与基本控制逻辑入门
1.LED灯模块如何点亮
入门的话也是从这个模块开始的,这里简述一下LED灯的工作原理
如图所示右边的是全部接了高电平接了电阻,左边只要是低电平即可形成通路 ①
P00---->P07对应板子上的L1-L8
可以这样看
P07 P06 P05 P04 P03 P02 P01 P00
L8 L7 L6 L5 L4 L3 L2 L1
表1.1如上所示
例如我们要让L8和L4同时亮,其它灯同时灭,根据①(①就是指上边下划线那块),我们要让L8和L4对于的P0上引脚变成低电平,左边低右边高,形成了通路,LED灯就亮了。
所以对照表1.1,我们自己写一下
P07 P06 P05 P04 P03 P02 P01 P00
L8 L7 L6 L5 L4 L3 L2 L1
0 1 1 1 0 1 1 1
P0=0111 0111(二进制)
为了方便会用十六进制表示(二进制转化16进制就是每四位求和然后连起来,不会二进制转化16进制的可以搜一下)
P0=0x77;
有个小技巧,灯都是关的值是0xff,L8亮了L8站的位置置0,如果其它的不亮,就是15(0xff-0x08)-8=7
再说,如果L7不亮,就是15-4=11,11转化成16进制就是b其它的不良是0xbf
如果L7,L8都不亮,就是15-8-4=3,0x3f
低位也是同理,这样就很快,不亮的多了减去一些,多了的就从小的开始加,对于我这样脑子转不过来的来说,这方法相当好用
所以说,目前来看,让LED灯L8和L4同时亮我们只需要如下几步:②
- 列出我们要控制的引脚与实际LED灯的对照表
- 把数值根据要亮灯的情况填写进去,根据①,让谁亮谁就填0
- 将表中二进制数转化为十六进制数,把值赋给P0
2.蓝桥杯开发板的总线控制逻辑1
按理来讲我们控制P0引脚就可以按照我们的想法控制LED灯的亮灭了,但这时我们可以看一下图0.1,小小一块板子布满了密密麻麻的各种外设,我们如何保证用P0端口控制LED灯的时候不影响到其它外设呢?
这时候我们再看图1.1,我们发现P0端口并不是直接和LED灯连接起来的,中间还经过了一个芯片。这个芯片有什么作用呢?我们可以把它看作一个开关,当开关打开时,我们可以的P0引脚可以直接连接到LED灯上,这时候我们控制P0引脚就可以控制各LED灯的亮灭了,但当它关掉时,各LED灯会保持关闭前的高低电平不在变化,P0引脚将不能再连接到各个LED灯了,直到下一次打开这个开关。
有三种办法我们可以了解到它:
- 我们找到芯片的电路图和芯片手册,顺着它P0引脚捋一遍,找到整一条线上的各种开关,如何按顺序打开它。这一般是大神玩家的操作,在有一定功底之后打开手册一看就懂,不过新手刚刚入行,一上来就看这么大一串手册多半会头晕,比如说我。
- 找一个教程,带着大家看手册走一遍,相当于帮你提取了信息,只要按照提示走一遍就可以了。
- 有一个人告诉你,这样操作,记住就行。
对于自学者来说,这三者难度是递减的,对于教学这来说也同样如此,难度递减意味着知道的快,但知道的越快其实懂得东西越少,比如说下次换个板子,就不知道如何使用了。
我比较笨,推荐一个简单的套路,先找个简单的操作运行一下看看效果,改一改看看如何变化,再找一个进阶的教程了解原理,再自己写一遍。
如果对自己比较自信可以直接找个进阶的教程对着做。
说回刚才的问题,由于板子上挂了太多的芯片,能控制各种芯片的引脚数目又有限(就像是两只手拿十来个篮球很费劲拿不了),所以引入了译码器来控制各个锁存器。
为什么拿译码器来控制?
一个38译码器,输入三个数,最多产生8个数字,即23,然后输出值7个0一个1,相当于拿三条线,控制八个开关,像是图0.1所示,那么多芯片挂在一个板子上,用这种方法就可以实现用有限的引脚控制更多的设备(n个引脚控制2n个开关)。
实际的电路连接图下
根据这个图,我们可以看到,LED灯由P2端口与P0两个端口共同控制,
-
首先P2端口给译码器赋值,打开P0端口控制LED灯的开关Y4C
如何打开P2端口呢?我们参考②所示的部分(②在上边黑体加粗了)
1.1先列出一个表,如下所示(x表示无论是0或者是1对这里都不影响)
P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方
1 0 0 x x x x x 4 Y4C
这里我们可以假设P2端口为1000 0000 ,故P2的值可以为P2=0x80;
-
P0端口赋值打开对应的LED灯
-
赋值结束后我们已经达到让我们指定的LED灯亮的目的,此时不在需要继续打开开关,我们可以关闭开关,直到下次改变LED灯的值
3.1先列出一个表,如下所示(x表示无论是0或者是1对这里都不影响)
P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方
0 0 0 x x x x x 4 Y4C
同理,故P2可以是P2=0x00;
3.实战操作与示例程序演示
在这里我们知道了它的原理让我们来写一个测试程序看看如何用c语言对其进行控制。
-
控制LED灯L8有规律的闪烁
#include <stc15f2k60s2.h> #include <intrins.h> /* 列表得到P0端口的值 P07 P06 P05 P04 P03 P02 P01 P00 L8 L7 L6 L5 L4 L3 L2 L1 0 1 1 1 1 1 1 1 L8亮的时候,P0=0x7f L8灭时 1 1 1 1 1 1 1 1 P0=0xff; P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方 1 0 0 x x x x x 4 Y4C 打开P0控制LED开关的值可以为 P2=0x80 */ //以下延时函数可在stc_isp软件中软件延时计算器中生成 void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); _nop_(); i = 43; j = 6; k = 203; do { do { while (--k); } while (--j); } while (--i); } void main(){ P2=0x80;//打开开关 while(1){ P0=0x7f;//L8亮 Delay1000ms();//延时1秒 P0=0xff;//L8灭 Delay1000ms();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁) } }
4.好了,到这里我们就知道如何控制LED灯了,让我们做一些简单的练习吧~
到这里的时候先不要看参考答案,试着在第三部分的示例的基础上修改做出答案。
-
控制L1有间隔的闪烁
-
从L1到L8每次循环间隔闪烁(流水灯)
-
实现如下所示的间隔闪烁
亮 灭 灭 灭 | 灭 灭 灭 亮—>灭 亮 灭 灭 | 灭 灭 亮 灭—>灭 灭 亮 灭 | 灭 亮 灭 灭—>灭 灭 灭 亮 | 亮 灭 灭 灭 —>亮 灭 灭 灭 | 灭 灭 灭 亮…
参考答案
方法还有很多,以下示例可以参考,如用其它方法实现也是很好的
-
so easy,略
-
流水灯简介代码1
#include <stc15f2k60s2.h> #include <intrins.h> /* 列表得到P0端口的值 P07 P06 P05 P04 P03 P02 P01 P00 L8 L7 L6 L5 L4 L3 L2 L1 P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方 1 0 0 x x x x x 4 Y4C 打开P0控制LED开关的值可以为 P2=0x80 */ //以下延时函数可在stc_isp软件中软件延时计算器中生成 void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); _nop_(); i = 43; j = 6; k = 203; do { do { while (--k); } while (--j); } while (--i); } void main(){ int i =1; P2=0x80;//打开开关 while(1){ P0=0x01<<i;//每次左移一位 P0=~P0;//因为是低电平亮,所以取反,取反后亮 if(++i==8) i=0; Delay1000ms();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁) } }
流水灯简介代码2
#include <stc15f2k60s2.h> #include <intrins.h> /* 十六进制对应表 10 11 12 13 14 15 a b c d e f 列表得到P0端口的值 P07 P06 P05 P04 P03 P02 P01 P00 L8 L7 L6 L5 L4 L3 L2 L1 值 0 1 1 1 1 1 1 0 0x7e 1 0 1 1 1 1 0 1 0xbd 1 1 0 1 1 0 1 1 0xdb 1 1 1 0 0 1 1 1 0xe7 P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方 1 0 0 x x x x x 4 Y4C 打开P0控制LED开关的值可以为 P0=0x80 */ unsigned char led_list1[]={~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80}; unsigned char led_list2[]={0x7e,0xbd,0xdb,0xe7 }; //以下延时函数可在stc_isp软件中软件延时计算器中生成 void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); _nop_(); i = 43; j = 6; k = 203; do { do { while (--k); } while (--j); } while (--i); } void main(){ unsigned char i =0; P2=0x80;//打开开关 while(1){ P0=led_list1[i];//L8亮 if(++i==8) i=0; Delay1000ms();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁) } }
-
练习3示例代码
#include <stc15f2k60s2.h> #include <intrins.h> /* 十六进制对应表 10 11 12 13 14 15 a b c d e f 列表得到P0端口的值 P07 P06 P05 P04 P03 P02 P01 P00 L8 L7 L6 L5 L4 L3 L2 L1 值 0 1 1 1 1 1 1 0 0x7e 1 0 1 1 1 1 0 1 0xbd 1 1 0 1 1 0 1 1 0xdb 1 1 1 0 0 1 1 1 0xe7 P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方 1 0 0 x x x x x 4 Y4C 打开P0控制LED开关的值可以为 P0=0x80 */ unsigned char led_list1[]={~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80}; unsigned char led_list2[]={0x7e,0xbd,0xdb,0xe7 }; //以下延时函数可在stc_isp软件中软件延时计算器中生成 void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); _nop_(); i = 43; j = 6; k = 203; do { do { while (--k); } while (--j); } while (--i); } void main(){ unsigned char i =0; P2=0x80;//打开开关 while(1){ P0=led_list2[i];//L8亮 if(++i==4) i=0; Delay1000ms();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁) } }
想不出练习3的代码也无妨,试着把他烧录到板子上,体验一下更加神奇的自定义类型的流水灯~
5.常见错误小总结
#incldue <stc15f2k60s2.h>
/*
列表得到P0端口的值
P07 P06 P05 P04 P03 P02 P01 P00
L8 L7 L6 L5 L4 L3 L2 L1
0 1 1 1 1 1 1 1
L8亮的时候,P0=0x7f
L8灭时
1 1 1 1 1 1 1 1
P0=0xff;
P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方
1 0 0 x x x x x 4 Y4C
打开P0控制LED开关的值可以为
P2=0x80
*/
//以下延时函数可在stc_isp软件中软件延时计算器中生成
void mian(){
P2=0x40;//打开开关
while(1){
P0=0x7f;//L8亮
Delay1000ms();//延时1秒
P0=0xff;//L8灭
}
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 43;
j = 6;
k = 203;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
上述代码问题重重,不出意外的话里边有你踩过的坑(别问我咋知道的我都踩过这些坑…(痛苦))
-
include 拼写错误
-
main函数拼写错误,写成了mian,注意,keil编辑器不会提示main函数错误,它可能认为mian也是用户自己写的函数
-
main函数中调用的函数没有放在main函数前或未提前声明
-
Delay1000ms函数分号使用了中文标点
-
逻辑错误,P0赋值后并未进行延时,循环快速跳转到第一句,P0在很短时间内重新赋值,相当于没赋值,显示结果为L8常亮
-
逻辑错误,P2开LED开关的赋值应为P2=1 00 x xxxx 可以为P2=0x80,但不可以为 0100 0000
-
用到了_nop_()却没有引入<intrins.h>头文件
曾经的我作为一名小菜鸟刚刚学这个,曾同时出现了多个上述错误,并把正确的地方反复修改,然后。。。可想而知很痛苦
6.代码洁癖
对于新手而言,以上代码确实足够做第一节课的入门了,但我认为,上述代码还不够好,至少逻辑不够明确,且难以复用,换句话说,如果是下次再加入一个数码管显示的代码,那么要改动的地方还是不少的,如果LED换个方法显示,可能又要改很多地方等等,有没有一种可能我们可以进一步简化我们的代码,并使他可以在下一次进一步得到复用呢?
理一理逻辑
#include <stc15f2k60s2.h>
#include <intrins.h>
/*
十六进制对应表
10 11 12 13 14 15
a b c d e f
列表得到P0端口的值
P07 P06 P05 P04 P03 P02 P01 P00
L8 L7 L6 L5 L4 L3 L2 L1 值
0 1 1 1 1 1 1 0 0x7e
1 0 1 1 1 1 0 1 0xbd
1 1 0 1 1 0 1 1 0xdb
1 1 1 0 0 1 1 1 0xe7
P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方
1 0 0 x x x x x 4 Y4C
打开P0控制LED开关的值可以为
P0=0x80
*/
unsigned char led_list1[]={~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80};
unsigned char led_list2[]={0x7e,0xbd,0xdb,0xe7 };
//以下延时函数可在stc_isp软件中软件延时计算器中生成
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 43;
j = 6;
k = 203;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main(){
unsigned char i =0;
P2=0x80;//打开开关
while(1){
P0=led_list2[i];//L8亮
if(++i==4) i=0;
Delay1000ms();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁)
}
}
我们以上次的练习3的代码作为例子,我们一行行看下去,我们让P2=0x80用来打开led灯的控制开关,那么下一次我们如果要打开led灯的开关,我们是不是还要看一下P2应该给什么值,查一下表或者看一下之前的代码,然后给P2赋值呢?
在上述的代码中,我们只用到了LED灯,但我们知道板子上P0的引脚是公用的,它可以控制很多芯片,那么控制其它芯片的时候,如果LED的控制开关没关,是不是有可能同时也控制了LED呢?我们当然不希望出现这种情况,我们希望当我们给LED灯赋值的时候它才会赋值,其他时候保持我们之前给他的值。
所以我们是不是可以在赋值结束后关闭LED的控制开关呢?
同时,我们也知道板子上P2的引脚也是公共的,我们只用到了P27,P26,P25三个端口作为开关,那其它的端口如果被别的设备使用,我们在改变P2的值的时候有没有可能也影响到其它的设备呢?有没有一种方法可以仅仅控制我们需要的作为开关的这三个引脚呢?
我们可以通过与运算的方式先把三个引脚都置零,然后再通过或运算的方式把需要置1的引脚置一
先与0x1f P2 & 0001 1111 与运算与1不改变原有值,与0无论是多少都会变成0—>P2=000x xxxx;
然后或 P2=P2|0x80 或运算或0不改变原有值,或1无论是多少都会变成1—>1000 0000
但实际上我们想要打开led的控制开关,并按照我们的意愿控制这8个LED的开关
void led_display(unsigned char num){
P2=(P2&0x1f)|0x80;//打开LED控制开关
P0=num;//按照我们的意愿控制开关
P2=(P2&0x1f);//关闭开关
}
这样来看我们给LED赋值的函数好像很好了,但我认为,他还能更好
你看,这个P2=0x80,如果我出去玩一会回来,我可能记不得他是干什么的了,我依稀只记得我想先打开开关,赋值,然后在关闭开关
这个时候如果我之前忘了写那行注释,我可能下一次再写就想不起来P2啥的
我喜欢这样来写
#define lock_led_open P2=(P2&0x1f)|0x80
#define lock_close P2=(P2&0x1f)
void led_display(unsigned char num){
lock_led_open;
P0=num;//按照我们的意愿控制开关
lock_close;
}
让我们来看看一番操作后的代码
#include <stc15f2k60s2.h>
#include <intrins.h>
/*
十六进制对应表
10 11 12 13 14 15
a b c d e f
列表得到P0端口的值
P07 P06 P05 P04 P03 P02 P01 P00
L8 L7 L6 L5 L4 L3 L2 L1 值
0 1 1 1 1 1 1 0 0x7e
1 0 1 1 1 1 0 1 0xbd
1 1 0 1 1 0 1 1 0xdb
1 1 1 0 0 1 1 1 0xe7
P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方
1 0 0 x x x x x 4 Y4C
打开P0控制LED开关的值可以为
P0=0x80
*/
unsigned char led_list1[]={~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80};
unsigned char led_list2[]={0x7e,0xbd,0xdb,0xe7 };
//以下延时函数可在stc_isp软件中软件延时计算器中生成
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 43;
j = 6;
k = 203;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
#define lock_led_open P2=(P2&0x1f)|0x80
#define lock_close P2=(P2&0x1f)
void led_display(unsigned char num){
P2=lock_led_open;
P0=num;//按照我们的意愿控制开关
P2=lock_close;
}
void main(){
unsigned char i =0;
while(1){
led_display(led_list2[i]);
if(++i==4) i=0;
Delay1000ms();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁)
}
}
我们甚至可以让他更有可读性
#include <stc15f2k60s2.h>
#include <intrins.h>
/*
十六进制对应表
10 11 12 13 14 15
a b c d e f
列表得到P0端口的值
P07 P06 P05 P04 P03 P02 P01 P00
L8 L7 L6 L5 L4 L3 L2 L1 值
0 1 1 1 1 1 1 0 0x7e
1 0 1 1 1 1 0 1 0xbd
1 1 0 1 1 0 1 1 0xdb
1 1 1 0 0 1 1 1 0xe7
P27 P26 P25 | P24 P23 P22 P21 P20 Y值 译码器要打开的地方
1 0 0 x x x x x 4 Y4C
打开P0控制LED开关的值可以为
P0=0x80
*/
#define L1L8 0x7e
#define L2L7 0xbd
#define L3L6 0xdb
#define L4L5 0xe7
unsigned char led_list1[]={~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80};
unsigned char led_list2[]={ L1L8,L2L7,L3L6,L4L5 };
//以下延时函数可在stc_isp软件中软件延时计算器中生成
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 43;
j = 6;
k = 203;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
#define lock_led_open P2=(P2&0x1f)|0x80
#define lock_close P2=(P2&0x1f)
void led_display(unsigned char num){
lock_led_open;
P0=num;//按照我们的意愿控制开关
lock_close;
}
void main(){
unsigned char i =0;
while(1){
led_display(led_list2[i]);
if(++i==4) i=0;
Delay1000ms();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁)
}
}
啊,这该死的艺术感~