2022年蓝桥杯单片机入门系列1——数码管模块详解与较高级功能

0.实训平台布局

我们板子上一共有这些,现在从入门开始整理

图0.1

现在开始一个模块一个模块的整理,从入门开始。

初出茅庐·LED灯模块与基本控制逻辑入门

1.LED灯模块如何点亮

图1.1

入门的话也是从这个模块开始的,这里简述一下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同时亮我们只需要如下几步:②

  1. 列出我们要控制的引脚与实际LED灯的对照表
  2. 把数值根据要亮灯的情况填写进去,根据①,让谁亮谁就填0
  3. 将表中二进制数转化为十六进制数,把值赋给P0

2.蓝桥杯开发板的总线控制逻辑1

按理来讲我们控制P0引脚就可以按照我们的想法控制LED灯的亮灭了,但这时我们可以看一下图0.1,小小一块板子布满了密密麻麻的各种外设,我们如何保证用P0端口控制LED灯的时候不影响到其它外设呢?

这时候我们再看图1.1,我们发现P0端口并不是直接和LED灯连接起来的,中间还经过了一个芯片。这个芯片有什么作用呢?我们可以把它看作一个开关,当开关打开时,我们可以的P0引脚可以直接连接到LED灯上,这时候我们控制P0引脚就可以控制各LED灯的亮灭了,但当它关掉时,各LED灯会保持关闭前的高低电平不在变化,P0引脚将不能再连接到各个LED灯了,直到下一次打开这个开关。

有三种办法我们可以了解到它:

  1. 我们找到芯片的电路图和芯片手册,顺着它P0引脚捋一遍,找到整一条线上的各种开关,如何按顺序打开它。这一般是大神玩家的操作,在有一定功底之后打开手册一看就懂,不过新手刚刚入行,一上来就看这么大一串手册多半会头晕,比如说我。
  2. 找一个教程,带着大家看手册走一遍,相当于帮你提取了信息,只要按照提示走一遍就可以了。
  3. 有一个人告诉你,这样操作,记住就行。

对于自学者来说,这三者难度是递减的,对于教学这来说也同样如此,难度递减意味着知道的快,但知道的越快其实懂得东西越少,比如说下次换个板子,就不知道如何使用了。

我比较笨,推荐一个简单的套路,先找个简单的操作运行一下看看效果,改一改看看如何变化,再找一个进阶的教程了解原理,再自己写一遍。

如果对自己比较自信可以直接找个进阶的教程对着做。

说回刚才的问题,由于板子上挂了太多的芯片,能控制各种芯片的引脚数目又有限(就像是两只手拿十来个篮球很费劲拿不了),所以引入了译码器来控制各个锁存器。

LBsik.png

图1.2

为什么拿译码器来控制?

一个38译码器,输入三个数,最多产生8个数字,即23,然后输出值7个0一个1,相当于拿三条线,控制八个开关,像是图0.1所示,那么多芯片挂在一个板子上,用这种方法就可以实现用有限的引脚控制更多的设备(n个引脚控制2n个开关)。

实际的电路连接图下

LB6HO.png

图1.3

根据这个图,我们可以看到,LED灯由P2端口与P0两个端口共同控制,

  1. 首先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;

  2. P0端口赋值打开对应的LED灯

  3. 赋值结束后我们已经达到让我们指定的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灯了,让我们做一些简单的练习吧~

到这里的时候先不要看参考答案,试着在第三部分的示例的基础上修改做出答案。

  1. 控制L1有间隔的闪烁

  2. 从L1到L8每次循环间隔闪烁(流水灯)

  3. 实现如下所示的间隔闪烁

亮 灭 灭 灭 | 灭 灭 灭 亮—>灭 亮 灭 灭 | 灭 灭 亮 灭—>灭 灭 亮 灭 | 灭 亮 灭 灭—>灭 灭 灭 亮 | 亮 灭 灭 灭 —>亮 灭 灭 灭 | 灭 灭 灭 亮…

参考答案

方法还有很多,以下示例可以参考,如用其它方法实现也是很好的

  1. so easy,略

  2. 流水灯简介代码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. 练习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();//延时一秒(这里下一步会回到第一行,不加延时就无法闪烁)
	}
}

啊,这该死的艺术感~

  • 8
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值