蓝桥杯——单片机学习(4——数码管显示)

注:此单片机型号为 STC15F2K60S2.

原理

来源:我自己:)
STC15F2K60S2的数码管是八位数码管,首先先看他们的接口。
com 接口:位选接口,用来选择第1—8位数码管,一次只能选中一个。当com接口为1时,选中。
a——dp 接口:段选接口,用来选择数码管上的对应横竖LED灯管。当为0时,选中。dp为右下角的点。
注:所有的数码管的a—dp接口都是连在一起的。

来源:我自己:)
位选
以第一个数码管为例,要选中第一个数码管,就要使得com1为1,可控管脚为P00—P07,所以要输入P00为高电平。为了让P00能够影响到com1,就要使得锁存器 M74HC573M1R 导通,即要让管脚LE为高电平。又管脚LE与Y6C连接,所以Y6C也为高电平。
图片来源:我自己:)
又Y6C连接了一个 或非门,或非门的输入端为Y6和WR。因为WR已用跳线帽与GND连接在了一起,GND为接地,所以WR为0(低电平)。

l来源:我自己:)

根据或非门逻辑表格:

Y6	      WR	   Y6C
0	       0	     1
0          1   	     0
1	       0	     0
1	       1	     0

所以,当Y6C为0时,Y6为1;当Y6C为1时,Y6为0.
所以,要使得Y6C为高电平,Y6就要为低电平,即Y6为0.
来源:我自己:)
又Y6是由 74HC138 位译码器的输入端A、B、C来控制,以 C、B、A 为逻辑顺序,CBA组成二进制数,来选中Y0—Y7,被选中者即为低电平。

所以,要使得 Y6 为低电平,CBA 要为 110.

另外,由于 P0 口是复用的,意味着也要控制别的器件,所以如果一直让 LE 为高,那么就在控制别的器件的时候就会影响到 LED,所以在锁存器导通后,应该截止保存住它的状态。

段选
以点亮数字“1”为例,当要点亮数字“1”时,‘b1’‘c1’被选中,为低电平,其余则为高电平。
来源:我自己:)
由于’a1’—‘dp1’与‘a’—‘dp’相连,a’—‘dp’又通过锁存器M74HC573M1R与P0口相连。
来源:我自己:)
所以,要选中’b’‘c’,则P0=0xF9. //1111 1001
且还要锁存器导通,即LE=1,即Y7C=1.
在这里插入图片描述
来源:我自己:)
又Y7C通过或非门与Y7相连,所以Y7为0,所以CBA为111.

整合逻辑:
段选:
        com1 -> P0 = 0x01 ->LE=1 -> Y6C=1 -> 
      Y6=0 -> CBA=110 ->锁存CBA = 000
  
位选:
       valus = 1 -> 选中b,c -> P0 = 0xF9 -> LE=1 -> 
       Y7C=1 -> Y7=0 -> CBA=111 ->锁存CBA = 000

代码

蓝桥杯官方代码。

“seg.c”

#include "seg.h"
// 显示转换
void Seg_Tran(unsigned char *pucSeg_Buf, unsigned char *pucSeg_Code)
{
  unsigned char i, //i为段选数,com i
  j=0, temp;

  for(i=0; i<8; i++, j++)
  {
    switch(pucSeg_Buf[j])
    { // 低电平点亮段,段码[MSB...LSB]对应码顺序为[dp g f e d c b a] //LSB最低有效位和MSB最高有效位
      case '0': temp = 0xc0; break;
      case '1': temp = 0xf9; break;
      case '2': temp = 0xa4; break;
      case '3': temp = 0xb0; break;
      case '4': temp = 0x99; break;
      case '5': temp = 0x92; break;
      case '6': temp = 0x82; break;
      case '7': temp = 0xf8; break;
      case '8': temp = 0x80; break;
      case '9': temp = 0x90; break;
      case 'A': temp = 0x88; break;
      case 'B': temp = 0x83; break;
      case 'C': temp = 0xc6; break;
      case 'D': temp = 0xA1; break;
      case 'E': temp = 0x86; break;
      case 'F': temp = 0x8E; break;
      case 'H': temp = 0x89; break;
      case 'L': temp = 0xC7; break;
      case 'N': temp = 0xC8; break;
      case 'P': temp = 0x8c; break;
      case 'U': temp = 0xC1; break;
      case '-': temp = 0xbf; break;
      case ' ': temp = 0xff; break;
      default: temp = 0xff;
    }	
    if(pucSeg_Buf[j+1] == '.')
    {
      temp = temp&0x7f;
      j++;
    }
    pucSeg_Code[i] = temp ;
  }
}
// 数码管显示
void Seg_Disp(unsigned char *pucSeg_Code, unsigned char ucSeg_Pos)
{
  P0 = 0xff; 						// 消隐
  P2 = P2 & 0x1F | 0xE0;			// P27~P25清零,再定位Y7C
  P2 &= 0x1F;						// P27~P25清零
	
  P0 = 1<<ucSeg_Pos; 				// 位选
  P2 = P2 & 0x1F | 0xC0;			// P27~P25清零,再定位Y6C
  P2 &= 0x1F;						// P27~P25清零,锁存
	
  P0 = pucSeg_Code[ucSeg_Pos]; 		// 段码(段选,从段码数组中选择需要的字符)
  P2 = P2 & 0x1F | 0xE0;			// P27~P25清零,再定位Y7C
  P2 &= 0x1F;						// P27~P25清零
}

“tim.c”

#include "tim.h"

void Cls_Peripheral(void)   //关闭外设。
{
  P0 = 0xFF;
  P2 = P2 & 0x1F | 0x80;			// P27~P25清零,再定位Y4C
  P2 &= 0x1F;						// P27~P25清零
  P0 = 0;
  P2 = P2 & 0x1F | 0xA0;			// P27~P25清零,再定位Y5C
  P2 &= 0x1F;						// P27~P25清零
}

void Led_Disp(unsigned char ucLed)  //LED灯点亮
{ // IO模式(J13-2和J13-3相连)
  P0 = ~ucLed;
  P2 = P2 & 0x1F | 0x80;			// P27~P25清零,再定位Y4C
  P2 &= 0x1F;						// P27~P25清零
//XBYTE[0x8000] = ~ucLed;			// MM模式(J13-2和J13-1相连)
}

void Timer1Init(void)				// 1毫秒@12.000MHz
{
  AUXR &= 0xBF;						// 定时器1时钟12T模式,AUXR.1清零
  TMOD &= 0x0F;						// 设置定时器模式,软件驱动,定时器模式,m0m1为00排列,模式0
  TL1 = 0x18;						// 设置定时初值 (65536 - 1000) % 256
  TH1 = 0xFC;						// 设置定时初值 (65536 - 1000) / 256
  TF1 = 0;		 					// 清除TF1标志,溢出标志位
  TR1 = 1;			  				// 定时器1开始计时
  ET1 = 1;		  					// 允许定时器1中断
  EA = 1;		  					// 允许系统中断
}

main.c

// 运行程序时,将J13调整为io模式(1-2脚短接)
#include "tim.h"
#include "seg.h"
#include "stdio.h"

unsigned char ucSec, ucLed;
unsigned char pucSeg_Buf[9],   //字符型段选数组
 pucSeg_Code[8],               //字符型位选数组
 ucSeg_Pos;                    //位选数\段选数
unsigned long ulms;
// 注意:sprintf()会在字符串后面添加”\0”,所以pucSeg_Buf[]的长度应为9。
// 如果字符串中包含小数点,pucSeg_Buf[]的长度应为10。
void main(void)
{
  Cls_Peripheral();          //关闭外设
  Timer1Init();              //定时器1初始化

  while(1)
  {
    sprintf(pucSeg_Buf, "    %04u", (unsigned int)ucSec);   // %u    十进制无符号整数 ,向BUF数组里写入数据
    Seg_Tran(pucSeg_Buf, pucSeg_Code);//数码管转换
  }
}

void Time_1(void) interrupt 3
{
  ulms++;
  if(!(ulms % 1000))
  {
    ucSec++;//每秒加1

    ucLed ^= 1;//ucled第一位取反
    Led_Disp(ucLed);
  }
	
  Seg_Disp(pucSeg_Code, ucSeg_Pos);	//数码管显示
  if(++ucSeg_Pos == 8) ucSeg_Pos = 0;//当位选数为8时,清零(保持在0-7)
}

注释代码参考博客:
@love、reading——C 字符串函数 sprintf ()、snprintf () 详解
@myyllove——C/C++ % s % d % u 基本概念与用法

我自己的代码

单个数码管显示——实现数码管显示流水灯的对应位置

seg.c

/**************************************************************************** 
* Copyright (C), 2022,Moqim
* 文件名: seg.c 
* 内容简述:数码管显示函数 
* 
* 文件历史: 
* 版本号      日期       作者     说明 
*   01a    2022-04-26   Moqim   创建该文件
*   01b    2022-05-01   Moqim   使案例简单化
*/

#include "seg.h"

/* 定义全局变量,代表第一个数码管的字码值 */
unsigned char smg1;

/*数据选择区*/
/*"code"关键字,表示该数据不可修改*/ 
/* 0、1、2、3、4、5、6、7、8、9 */ 
unsigned char code Nixie[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};

/**************************************************************************** 
* 函数名: SEG_Scan() 
* 功 能: 动态点亮数码管
* 输 入: 无        
* 输 出: 无 
* 说 明:在数码管动态显示过程中,主要是在数码管位选和段选产生的瞬态造成的。
         数码管在不应该亮的地方有点微微发亮,这种现象就是 “鬼隐”。

         消除鬼隐(简称“消隐”)的办法:
                 关闭段:在改变值之前,使数码管全部熄灭。
                 关闭位:关闭数码管的位,等到赋值过程都做好了后,再重新打开。
*/
void SEG_Scan(void)
{
	/* 消隐 */
	P0 = 0xFF;
	P2 = P2 & 0x1F | 0xE0;
	P2 &= 0x1F;
	
	/* 位选 */
	P0 = 0x01; //选择第一个数码管
	P2 |= 0xC0;
	P2 &= 0x1F;
	
	/* 段选 */
	P0 = 0xFF; //消隐
	P2 = P2 & 0x1F | 0xE0;
	P0 =  Nixie[smg1]; //选择字码
	P2 &= 0x1F;
}

seg.h

#ifndef __SEG_H
#define __SEG_H

#include "stc15f2k60s2.h"

/* 声明变量,使其可以被调用该头文件的文件调用 */
extern unsigned char smg1;

void SEG_Scan(void);

#endif /*__SEG_H*/

main.c
//led.c文件参考上一篇博客:蓝桥杯 —— 单片机学习(3—— 点亮 LED 灯)

/**************************************************************************** 
* Copyright (C), 2022,Moqim
* 文件名: main.c 
* 内容简述:实现数码管显示流水灯的对应位置 
* 
* 文件历史: 
* 版本号      日期       作者     说明 
*   01a    2022-04-26   Moqim   创建该文件
*   01b    2022-05-01   Moqim   修改main函数
*/

//头文件调用区
#include "stc15f2k60s2.h"
#include "led.h"
#include "seg.h"

//变量定义区
int i = 0;

//函数声明区
void Delay_ms(unsigned int num);

//Main Body
int main(void)
{
	LED_Init();
	
	while (1)
	{
		LED_Ctrl(0x01 << i); //流水灯
		smg1 = i + 1;        //赋值数码管字码
		SEG_Scan();          //数码管显示
		i++;                  
		if (i == 8)i = 0;    //范围限制
		Delay_ms(1000);      //延时1s
	}
	
}

/**************************************************************************** 
* 函数名: Delay_ms() 
* 功 能: 延时
* 输 入: unsigned int num:延时num毫秒        
* 输 出: 无 
*/
void Delay_ms(unsigned int num)//软件延时不精确,凑合用吧
{
	int i,j;
	for(i = 0;i<num;i++)
	{
		for(j = 0;j<625;j++);
	}
}

老师的代码

while{
	/*消隐*/
	P0 = 0xFF;
	P2 = P2 & 0x1F | 0xC0;
	P2 &= 0x1F;

	/*位选*/
	P0 = 0x01;
	P2 = P2 & 0x1F | 0xC0;
	P2 &= 0x1F;

	/*消隐*/
	P0 = 0xFF;
	P2 = P2 & 0x1F | 0xE0;
	P2 &= 0x1F;
	/*段选*/
	//例:1010 0100
	P0 = 0xA4;
	P2 = P2 & 0x1F | 0xE0;
	P2 &= 0x1F;
	

}

显示‘2001’

#include <stdio.h>
#include <STC15F2K60S2.h>


//显示2001
int i;
int main(){
	while (1) {                       //死循环,防止程序跑飞;
	for (i = 0; i < 4; i++) {         //位选前四位;
		/*位选*/
		P0 = 1 << i;                  //选择com接口;
		P2 = P2 & 0x1F | 0xC0;        //选择Y6;
		P2 &= 0x1F;                   //锁存;
		/*消隐*/
		P0 = 0xFF;
		P2 |= 0xE0;
		P2 &= 0x1F;
		/*位选*/
		switch (i) {
		case 0:P0 = 0xA4; break;      //使得第一个数码管显示‘2’;
		case 1:P0 = 0x80; break;
		case 2:P0 = 0x80; break;
		case 3:P0 = 0xF9; break;
		}
		P2 |= 0xE0;                   //选择Y7
		P2 &= 0x1F;                   //锁存
		/*消隐*/
		P0 = 0xFF;
		P2 |= 0xC0;
		P2 &= 0x1F;
	}

}

}

问题

数码管亮度不一致,原因是最后一个的高电平时长远大于前面(执行完最后一个时就去执行别的了,而不是循环执行点亮)。
解决方法:数码管显示写在定时器里,使得每个数码管的占空比(高电平所占比率)一致。

注意

1、消隐不能只写一个 P0 = 0xFF,要写连通 Y 区的,把消隐导入进去。

/*消隐*/
P2 = P2 & 0x1F | 0xE0;
P0 = 0xFF;
P2 &= 0x1F;

2、简写的与或非要和等号靠在一起;

P2 &= 0xC0;               //正确
P2 & = 0xC0;              //错误
P2& = 0xC0;               //错误

3、尽量先写 P2 再写 P0.

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Moqim Flourite.

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值