51单片机通过8255A扩展控制数码管和LCD1602

本文介绍了如何使用51单片机控制数码管和LCD1602进行计数,同时利用8255A进行I/O扩展,包括共阳极和共阴极数码管的驱动,以及8255A的不同工作模式和与矩阵键盘的交互。
摘要由CSDN通过智能技术生成

0、引言

        51单片机的引脚有时无法满足工程对引脚数量的要求,常用的方法就是进行扩展IO口,本次分享的是我学习单片机时候学校开展的实训,主要功能是使用51单片机控制数码管和LCD1602进行计数,同时通过矩阵键盘进行控制,本文主要介绍数码管和8255A的驱动方式,并提供相关代码。首先先看一下最后的仿真图,如图1所示。

图1 仿真图

1、数码管

       数码管是一种用于显示数字的电子元件,通常由多个LED(发光二极管)组成。它们通常用于时钟、计数器、温度计等设备中,可以显示数字和一些特定的符号。数码管可以是共阳极或共阴极的,共阳极的数码管在显示数字时,需要通过给对应的LED灯的阴极加电压来点亮,而共阴极的数码管则需要给对应的LED灯的阳极加电压来点亮。数码管通常由7段或14段LED组成,可以显示0-9的数字和一些字母。综上所述,数码管由LED组成,所以控制方式和控制LED的方式相同,区别在与控制数码管是同时控制多个LED以显示不同数据。本文介绍最基础的一位数码管。(多位数码管和一位数码管大同小异,可能会加上片选信号。)

共阳极数码管和共阴极数码管是两种常见的数码管类型,它们的工作原理略有不同。

共阳极数码管:在共阳极数码管中,所有的阳极都连接在一起,而每个LED的阴极则分别连接到控制电路。

共阴极数码管:在共阴极数码管中,所有的阴极都连接在一起,而每个LED的阳极则分别连接到控制电路。

两种数码管的工作原理相反,但它们都可以用于显示数字和一些特定的符号。选择使用哪种类型的数码管取决于具体的应用需求和设计考虑。图2为二者原理图。

图2 共阴极与共阳极数码管原理图

图3 一位数码管

//共阳极
char code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};   //0-F
//共阴极
 char code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};   //0-F

2、8255A

       8255A是一种集成电路芯片,通常被用作并行输入/输出(I/O)接口。它由英特尔(Intel)公司设计和生产,它包含了三个8位I/O端口,分别为端口A、端口B和端口C。每个端口都可以配置为输入或输出,并且可以通过编程来控制数据的传输和处理。此外,8255A还包含了一些控制寄存器,用于配置和控制I/O端口的工作模式和功能。(51单片机与8255A的连接方式可参考图1)

图4 8255A的功能结构

(1)数据总线缓冲器

D7~D0与系统数据总线相连,负责与CPU进行数据交换。包括输入输出数据、控制字和状态字。

(2)读/写控制逻辑

接收来自CPU的地址信息和控制信息。

RD:读信号输入引脚,低电平有效。

WR:写信号输入引脚,低电平有效。

(3)A组控制和B组控制

这两组控制逻辑电路接收来自CPU的控制字,控制两组端口的工作方式及读/写操作。A组控制端口A和端口C的高4位,B组控制端口B和端口C的低4位。

A1、A0:端口选择信号输入引脚。

(4)端口A、B、C

8255A有3个8位数据输入/输出端口:端口A、端口B和端口C,分别简称为A口、B口和C口。它们对外的引线分别是PA7~PA0、PB7~PB0和PC7~PC0。C口可分成两个4位的端口:C口高4位(PC7~PC4)和C口低4位(PC3~PC0)。

RESET:复位信号输入引脚,高电平有效。用于将8255A控制字寄存器清“0”,并将A、B、C口置成输入状态。

CS:片选信号输入引脚,低电平有效。

工作方式

8255A可以工作在三种不同的模式下,分别是模式0、模式1和模式2。

在模式0(基本输入输出方式)下,端口A和端口B分别作为并行输入和输出端口,而端口C可以作为两个独立的4位I/O端口。

在模式1(选通输入输出方式)下,端口A和端口B分别作为并行输入和输出端口,而端口C作为双向总线端口。

1、(选通输出)在方式1下,A口、B口都可以选通输出数据,C口的6条线作为选通控制信号:PC3、PC6、PC7(INTR、ACK、OBF)配合A口,PC0、PC1、PC2(INTR、ACK、OBF)配合B口;OBF:输出缓冲器满信号,低电平有效,当A口/B口有数据时,OBF就会变成低电平,高电平时,CPU可往A口/B口放送数据。ACK:外设收到数据的应答信号,低电平有效。INTE:中断允许状态。(工作原理:与PC7通过与门设置PC3的值,当OBF为高电平时,表示CPU可向外设传数据,但CPU迟迟为将数据传来,此时可通过PC6将INTE设置为高电平,两者通过与门后,将PC3置为高电平,向CPU发起中断请求信号(实际上是提醒CPU往外设传输数据)。INTR:中断请求信号,高电平有效。

2、(查询方式)查询端口C的BIT7,如果是0则等待;如果不是0,则CPU发送数据。

3、(中断方式)PC3置1,请求中断置位PC6,允许中断数据从端口输出,导致OBF变成高电平,证明端口数据被取走,CPU就会往端口放送数据,INTE与PC7构成一个与门,导致PC3再次置1。

在模式2(双向输入输出)下,只有A口可以工作在该模式下

根据具体的应用需求,可以选择合适的工作模式。(模式0不能工作在中断方式、模式1不能工作在无条件传输方式下)其方式控制字如图5所示。

图5 8255A控制字

附录代码

#include <reg52.h>
#include <stdio.h>
#include "absacc.h"


#define uchar unsigned char    //宏定义
#define uint unsigned int      //宏定义
/*
8255宏定义
A口
B口
C口
控制口
*/

#define PA XBYTE[0X0000]
#define PB XBYTE[0X0400]
#define PC XBYTE[0X0800]
#define PCTL XBYTE[0X0C00]

/*
1602引脚定义
*/
sbit LcdRs_P   = P2^6;			// 1602液晶的RS管脚       
sbit LcdRw_P   = P2^5;			// 1602液晶的RW管脚 
sbit LcdEn_P   = P2^4;			// 1602液晶的EN管脚

code P1_scan[]={0x7f,0xbf,0xdf,0xef}; //按键扫描数组
code key_temp_value[]={0xee,0xed,0xeb,0xe7,0xde,0xdd,0xdb,0xd7,0xbe,0xbd,0xbb,0xb7,0x7e,0x7d,0x7b,0x77};   //按键按下所对应的值
code uchar tmpled[8] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; 
uchar duan[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //数码管
uchar wei[]  ={0xfe,0xfd,0xfb,0xf7,0xef,0xdf};
uchar count = 0;    //全局变量,控制定时次数,count=20,为定时1s到了
uchar hour,min,sec;
uchar display_time[6] = {0,0,0,0,0,0};

//定时器0初始化函数
void Timer0_Init(void)		//50毫秒@12.000MHz
{
   TMOD = 0x01;		//设置定时器模式-定时器0工作在模式1,16位
   TL0 = (65536-50000)%256;		//设置定时初值
   TH0 = (65536-50000)/256;		//设置定时初值
   TF0 = 0;		//清除TF0标志
   TR0 = 1;		//定时器0开始计时
}

/*延时函数*/
void delay(uint x)
{
   uchar i;
   while(x--)
   {
      for(i=0;i<120;i++);
   }
}

//显示函数
void display()
{
   uchar i;
   display_time[0] = hour/10;
   display_time[1] = hour%10;
   display_time[2] = min/10;
   display_time[3] = min%10;
   display_time[4] = sec/10;
   display_time[5] = sec%10;
   for (i=0;i<6;i++)
   {
      //调试时发现,对于共阳极数码管,先送段码,后送位码,显示不正确。
      PB = wei[i];
      if (i==1||i==3)
      PA = duan[display_time[i]]+0x80;
      else
	 PA = duan[display_time[i]];      
      delay(2);     
   }
}


//时间调整函数
void time_adjust(uchar pos)
{
   if(pos==0)
   {
      hour++; if(hour>=24) hour = 0;
   }
   if(pos==1)
   {      
      if (hour==0) hour = 23;
      else hour--;	    
   } 
   if(pos==2)
   {
     min++; if(min>=60) min = 0;
   } 
   if(pos==3)
   {
      if(min==0) min = 59;
      else min--;
   }
   if(pos==4)
   {
      sec++; if(sec>=60) sec = 0;
   }
   if(pos==5)
   {
      if(sec==0) sec = 59;
      else sec--;
   }
   if(pos==6)
   {
      TR0 = 1;
   }
   if(pos==7)
   {
      TR0 = 0;
   }
}



//键盘扫描函数,此函数应该修改为按下抬起后有效
void key_scan()	 
{
   uchar i,j;
   for(i=0;i<4;i++)	//让每个列线出现低电平(按键扫描数据)
   {
      P1=P1_scan[i];
      if(P1!=P1_scan[i])
      {
	 delay(3);		//消抖
	 for (j=0;j<16;j++)
	 {
	    if (P1==key_temp_value[j])
	    {
	       while(P1==key_temp_value[j]);
	       time_adjust();	  
	    }
	 }   
      }
   }
}

/*********************************************************/
// 毫秒级的延时函数,time是要延时的毫秒数
/*********************************************************/
void DelayMs(uint time)
{
	uint i,j;
	for(i=0;i<time;i++)
		for(j=0;j<112;j++);
}


/*********************************************************/
// 1602液晶写命令函数,cmd就是要写入的命令
/*********************************************************/
void LcdWriteCmd(uchar cmd)
{ 
	LcdRs_P = 0;
	LcdRw_P = 0;
	LcdEn_P = 0;
	PC=cmd;
	DelayMs(2);
	LcdEn_P = 1;    
	DelayMs(2);
	LcdEn_P = 0;	
}


/*********************************************************/
// 1602液晶写数据函数,dat就是要写入的数据
/*********************************************************/
void LcdWriteData(uchar dat)
{
	LcdRs_P = 1; 
	LcdRw_P = 0;
	LcdEn_P = 0;
	PC=dat;
	DelayMs(2);
	LcdEn_P = 1;    
	DelayMs(2);
	LcdEn_P = 0;
}


/*********************************************************/
// 液晶光标定位函数
/*********************************************************/
void LcdGotoXY(uchar line,uchar column)
{
	// 第一行
	if(line==0)        
		LcdWriteCmd(0x80+column); 
	// 第二行
	if(line==1)        
		LcdWriteCmd(0x80+0x40+column); 
}



/*********************************************************/
// 液晶输出字符串函数
/*********************************************************/
void LcdPrintStr(uchar *str)
{
	while(*str!='\0')
			LcdWriteData(*str++);
}

//平方
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}
 
/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LcdGotoXY(Line,Column);
	for(i=Length;i>0;i--)
	{
		LcdWriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}


/*********************************************************/
// 1602液晶功能初始化
/*********************************************************/
void LcdInit()
{
	LcdWriteCmd(0x38);        // 16*2显示,5*7点阵,8位数据口
	LcdWriteCmd(0x0C);        // 开显示,不显示光标
	LcdWriteCmd(0x06);        // 地址加1,当写入数据后光标右移
	LcdWriteCmd(0x01);        // 清屏
}

/*********************************************************/
// 开启LCD1602显示
/*********************************************************/
void LcdOn()   //开显示器,关光标与闪烁
{
	LcdWriteCmd(0x0c);
}
/*********************************************************/
//关闭LCD1602显示,但DDRAM中内容不丢失,重开后将恢复内容

/*********************************************************/
void LcdOff()  
{
	LcdWriteCmd(0x08);
}

/*********************************************************/
// 1602液晶显示内容初始化
/*********************************************************/
void LcdShowInit()
{
	//LcdGotoXY(0,0);	    							// 定位到第0行第0列
	//LcdPrintStr("  sec ");	// 第0行显示“            ”
	//LcdGotoXY(1,0);	    							// 定位到第1行第0列
	//LcdPrintStr("ABCDEFGHIJKLMNOP");	// 第1行显示“ ”
}



void int_timer0(void) interrupt 1  //定时器0中断服务程序
{
   TL0 = (65536-50000)%256;		//设置定时初值
   TH0 = (65536-50000)/256;		//设置定时初值
   count++;   
   if (count==20)
   {
      count = 0;
      sec++;
      if (sec>=60)
      {
	 sec = 0;
	 min++;
	 if (min>=60)
	 {
	    min = 0;
	    hour++;
	    if (hour>=24)
	    {
	       hour = 0;
	    }
	 }
      }      
   }  
}
/*
函数名称:主函数 
作者:
时间:2024年1月5日
修改:无

*/

void main(void)
{    
   EA = 1;
   ET0 = 1;
   PCTL = 0x80;	            //设置8255工作方式
	 LcdInit();								// 液晶功能初始化	
   Timer0_Init(); 
	 LcdShowInit();
	 LcdGotoXY(0,2);	    							// 定位
	 LcdPrintStr(":");	// 第1行显示
   LcdGotoXY(0,5);	    							// 定位
	 LcdPrintStr(":");	// 第1行显示
   while(1)
   {
     key_scan();
     display();
		 LCD_ShowNum(0,0,hour,2);
		 LCD_ShowNum(0,3,min,2);
		 LCD_ShowNum(0,6,sec,2);
   }  
}


本文正好借助我实训的任务,给大家介绍一下8255和数码管的相关知识,矩阵键盘、LCD1602等相关知识寒假期间我也会逐步上传至平台,期待和大家一起进步!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值