2019-12-20 12:30:07 CWQLTYH 阅读数 174
  • 《计算机体系结构 硬件篇2》之 计算机通信

    第一阶段:《计算机体系结构》课程 分成4篇:分别是硬件篇,软件篇,网络篇,行业篇 第二阶段:《嵌入式技术》课程 分成5部分:Linux基础,C语言基础,C语言,Linux系统编程/网络编程,8051单片机,单片机,linux嵌入式,安卓,项目

    4200 人正在学习 去看看 张先凤

基于51单片机脉冲宽度调制(即PWM)直流电机转速快慢以及霍尔测速的项目

一、前言

1、基于51单片机脉冲宽度调制(即PWM)直流电机转速快慢以及霍尔测速的项目包括用Keil软件编写单片机C语言程序和用Proteus软件仿真单片机外围电路

2、基于51单片机脉冲宽度调制(即PWM)直流电机转速快慢以及霍尔测速的项目构思

(1)、实现什么功能:LCD1602字符型液晶显示器先显示时分秒,在点击移位按键后,停止计时,LCD1602字符型液晶显示器光标闪烁,每次按下移位键,LCD1602字符型液晶显示器光标随每次按下移位键移位到时分秒个位十位处闪烁,当LCD1602字符型液晶显示器的时分秒个位十位有闪烁后,可修改LCD1602字符型液晶显示器的时分秒个位十位闪烁位上的数值,再通过移位按键或启停按键启动或停止计时。

(2)、如何实现功能:如何让LCD1602字符型液晶显示器先显示时分秒:采用定时器0工作方式1计时,计时得到的数值赋给LCD1602字符型液晶显示器显示时间两位数分解函数进行分解,分解得到的个位数值与十位数值用变量代替分别由LCD1602字符型液晶显示器写指令函数和LCD1602字符型液晶显示器写数据函数写入LCD1602字符型液晶显示器,通过LCD1602字符型液晶显示器显示出来。如何让LCD1602字符型液晶显示器的时分秒个位十位闪烁:采用按键扫描函数扫描,每次按下移位键(此时定时器0停止计时),执行LCD1602字符型液晶显示器光标闪烁指令和LCD1602字符型液晶显示器光标随每次按下移位键移位到时分秒个位十位处闪烁的位置指令。如何修改LCD1602字符型液晶显示器的时分秒个位十位闪烁位数值并在原计时基础上实现计时:当移位键按下后,定时器0停止计时,计时得到的数值采用两位数分解公式(如:shi=num/10,ge=num%10。)进行分解,分解得到的个位数值与十位数值用变量代替写入按键扫描函数,此时也要采用两位数合成公式(如:miao=shi+ge=(num/10)*10+num%10)求出原先的计时数值,用变量代替原先的计时数值写入按键扫描函数(原因:时分秒的个位或十位显示位的数值是在原计时数值上分解得来,当时分秒的个位或十位显示位的数值发生变化后,原计时数值也要变化,否则启动定时器0计时后,LCD1602字符型液晶显示器显示的是原计时时间,而不是修改时分秒的个位或十位显示位的数值后的计时,因此要采用两位数合成公式(miao=shi+ge=(num/10)*10+num%10)求出原先的计时数值,用变量代替原先的计时数值写入按键扫描函数。)。

二、基于51单片机脉冲宽度调制(即PWM)直流电机转速快慢以及霍尔测速的项目的Keil软件编写的单片机C语言程序

#include<reg52.h>//单片机头文件
#define uchar unsigned char//宏定义,用uchar表示unsigned char,叫无符号字符型数据类型,取值范围为:0到255。
#define uint unsigned int//宏定义,用uint表示unsigned int,叫无符号整数型数据类型,取值范围为:0到65535。 
sbit RS=P2^5;//位定义LCD1602字符型液晶显示器的数据/命令选择引脚                 
sbit RW=P2^6;//位定义LCD1602字符型液晶显示器的读写引脚                        
sbit EN=P2^7;//位定义LCD1602字符型液晶显示器的使能引脚   			  			  	
sbit qitingjian=P1^0;//位定义启停键
sbit zhengzhuanjian=P1^1;//位定义正转键
sbit fanzhuanjian=P1^2;//位定义反转键
sbit jiasujian=P1^3;//位定义加速键
sbit jiansujian=P1^4;//位定义减速键
sbit zhengzhuan=P2^0;//正转端口
sbit fanzhuan=P2^1;//反转端口
sbit zhuansuled=P2^2;//位定义转速指示灯 
sbit qitingled=P3^0;//位定义启停指示灯
sbit zhengzhuangled=P3^1;//位定义正转指示灯
sbit waibuzhongduanling=P3^2;//位定义外部中断0端口
sbit fanzhuangled=P3^3;//位定义反转指示灯
uint qian,bai,shi,ge,zhuansu;//数码管千位变量、百位变量、十位变量、个位变量以及转速变量
uint pwmshi,pwmge;//脉冲宽度调制十位变量、个位变量		  			   
uchar pwm,Timer0count,Timer1count,maichongcount,qitingnum,zhengzhuanflag,fanzhuanflag;//脉冲宽度调制变量、定时器0定时计数变量、定时器1定时计数变量、脉冲计数变量、启停键按下数次变量、正转标志位变量、反转标志位变量
uchar code table0[]={'z','h','a','n','k','o','n','g','b','i',':'};//占空比字符串数组
uchar code table1[]={'z','h','u','a','n','s','u',':'};//转速字符串数组 
uchar code table2[]={'r','p','m'};//每分钟圈字符串数组   
  void Delay(uint z)//延时函数
{
   uint x,y;
   for(x=z;x>0;x--)
	for(y=110;y>0;y--);
 }
  void WriteCommand(uchar command)//LCD1602字符型液晶显示器写指令函数
{
   RS=0;//LCD1602字符型液晶显示器指令寄存器低电平
   RW=0;//LCD1602字符型液晶显示器写指令操作低电平
   P0=command;//LCD1602字符型液晶显示器写指令函数写指令到单片机P2组端口,再输送到LCD1602字符型液晶显示器指令寄存器中执行。
   Delay(5);//实际单片机演示,可以去掉延时。Proteus 仿真,需要保留延时。
   EN=1;//LCD1602字符型液晶显示器使能端高电平  
   Delay(5);//实际单片机演示,可以去掉延时。Proteus 仿真,需要保留延时。
   EN=0;//LCD1602字符型液晶显示器使能端低电平
 }
  void WriteData(uchar information)//LCD1602字符型液晶显示器写数据函数
{  
   RS=1;//LCD1602字符型液晶显示器数据寄存器高电平
   RW=0;//LCD1602字符型液晶显示器写数据操作低电平
   P0=information;//LCD1602字符型液晶显示器写数据函数写数据到单片机P2组端口,再输送到LCD1602字符型液晶显示器上显示。
   Delay(5);//实际单片机演示,可以去掉延时。Proteus 仿真,需要保留延时。
   EN=1;//LCD1602字符型液晶显示器使能端高电平 
   Delay(5);//实际单片机演示,可以去掉延时。Proteus 仿真,需要保留延时。
   EN=0;//LCD1602字符型液晶显示器使能端低电平
 }
  void LCD1602Init()//LCD1602初始化函数
{
   WriteCommand(0x38);//设置LCD1602字符型液晶显示器16x2显示,5*7或5*8点阵,8位数据并口,简称显模。
   WriteCommand(0x08);//设置LCD1602字符型液晶显示器显示开,简称亮屏。
   WriteCommand(0x01);//设置清除LCD1602字符型液晶显示器屏上所有内容,并让光标复位到LCD1602字符型液晶显示器屏左上角,简称清屏。
   WriteCommand(0x06);//设置开光标(或字符)地址指针自增1,光标右移动一个字符位,整屏显示不移动。
   WriteCommand(0x0c);//设置开LCD1602字符型液晶显示器显示,关光标,关光标(字符)闪烁。
 }
  void LCD1602InitDisplay()//LCD1602初始化显示函数
{
   uchar j;
   WriteCommand(0x80+0x00);//LCD1602字符型液晶显示器第一行第一个位置显示
   for(j=0;j<11;j++)//有10个字符,要循环10次。
  {
    WriteData(table0[j]);////显示占空比字符串
    Delay(2);
   }
   WriteCommand(0x80+0x40);//LCD1602字符型液晶显示器第二行第一个位置显示
   for(j=0;j<8;j++)//有8个字符,要循环8次。
  { 
    WriteData(table1[j]);//显示转速字符串
    Delay(2);
   }
   WriteCommand(0x80+0x4c);//LCD1602字符型液晶显示器第二行第十三个位置显示
   for(j=0;j<3;j++)//有8个字符,要循环8次。
  { 
    WriteData(table2[j]);//显示每分钟圈字符串
    Delay(2);
   }
 }
  void LCD1602DisplaySpeed(uint qian,bai,shi,ge)//LCD1602显示四位数速度分解函数
{
   LCD1602InitDisplay();//LCD1602初始化显示函数
   WriteCommand(0x80+0x0b);//LCD1602第一行第十二位显示位
   WriteData(pwmshi+0x30);//0x30表示字符库的数字0,整个代码表示十位可显示0到9的数字。
   WriteData(pwmge+0x30);//0x30表示字符库的数字0,整个代码表示十位可显示0到9的数字。
   WriteCommand(0x80+0x48);//LCD1602第二行第九位显示位
   WriteData(qian+0x30);//0x30表示字符库的数字0,整个代码表示千位可显示0到9的数字。
   WriteData(bai+0x30);//0x30表示字符库的数字0,整个代码表示百位可显示0到9的数字。
   WriteData(shi+0x30);//0x30表示字符库的数字0,整个代码表示十位可显示0到9的数字。
   WriteData(ge+0x30);//0x30表示字符库的数字0,整个代码表示个位可显示0到9的数字。
 }
  void TimerInit()//定时器初始化函数
{
   TMOD=0x11;//设定定时器类型为定时器0和定时器1,工作模式都为模式1。
   TH0=(65536-10000)/256;//TH0重装初值为65536-10000=55536,即定时1000微妙,相当于10毫秒。
   TL0=(65536-10000)%256;//TL0重装初值为65536-10000=55536
   TH1=(65536-50000)/256;//TH1重装初值为65536-10000=55536,即定时50000微妙,相当于50毫秒。
   TL1=(65536-50000)%256;//TL1重装初值为65536-10000=55536
   EA=1;//全局中断允许
   EX0=1;//开外部中断0允许
   IT0=1;//设置外部中断0下降触发中断
   ET0=1;//打开定时器0中断允许
   TR0=1;//开定时器0       
   ET1=1;//打开定时器1中断允许
   TR1=1;//开定时器1
 }
//  void Timer0Init()//定时器0初始化函数
//{
//   TMOD=0x01;//设定定时器0工作模式为模式1
//   TH0=(65536-10000)/256;//TH0装初值
//   TL0=(65536-10000)%256;//TL0装初值
//   EA=1;//开启总中断
//   ET0=1;//开启定时器0中断开关
//   TR0=1;//启动定时器0
// }
//  void Timer1Init()//定时器0初始化函数
//{
//   TMOD=0x10;//设定定时器1工作模式为模式1
//   TH1=(65536-50000)/256;//TH1装初值
//   TL1=(65536-50000)%256;//TL1装初值
//   EA=1;//开启总中断
//   ET1=1;//开启定时器1中断开关
//   TR1=0;//关定时器1
// }
  void Timer0() interrupt 1//定时器/计数器0中断函数,用来产生PWM。 
{  
   TH0=(65536-10000)/256;//TH0重装初值为65536-10000=55536,即定时10000微妙,相当于10毫秒。
   TL0=(65536-10000)%256;//TL0重装初值为65536-10000=55536
   Timer0count++;//定时器0定时计数变量先赋值再自加 
   if(Timer0count==10)//产生PWM波周期为100ms
  {
    Timer0count=0;//定时器0定时计数变量等于0
   }
   if(qitingnum==1)//判断启停键是否第一次按下
  {     
    if(zhengzhuanflag==1)//判断正转标志位变量是否为1
   {
     if(Timer0count<pwm)//定时器0定时计数变量对比占空比       
    {                               
      zhengzhuan=0;//开正转端口
      fanzhuan=1;//关反转端口
	  zhuansuled=0;//开转速指示灯
     }
     else
    {
      zhengzhuan=1;//关正转端口
	  fanzhuan=1;//关反转端口
      zhuansuled=1;//关转速指示灯
     }   
    }
    if(fanzhuanflag==1)//判断反转标志位变量是否为1
   {
     if(Timer0count<pwm)//定时器0定时计数变量对比占空比       
    {                               
      fanzhuan=0;//开反转端口
	  zhengzhuan=1;//关正转端口
	  zhuansuled=0;//开转速指示灯
     }
     else
    {
   	  fanzhuan=1;//关反转端口
	  zhengzhuan=1;//关正转端口
	  zhuansuled=1;//关转速指示灯
     }   
    }
   }
  }
  void Timer1() interrupt 3//定时器1中断函数,用来显示转速。
{
   TH1=(65536-50000)/256;//TH1重装初值为65536-10000=55536,即定时50000微妙,相当于50毫秒。
   TL1=(65536-50000)%256;//TL1重装初值为65536-10000=55536
   Timer1count++;//定时器1中断次数先赋值再加
   if(Timer1count==20)//判断定时器0中断次数是否为20次,相当于1秒钟。
  {
	Timer1count=0;//定时器1中断次数归零
	EX0=0;//关外部中断0
	zhuansu=55*maichongcount/24;//电机转速=每分钟X脉冲计数变量/每转一圈需要的脉冲个数,即55*maichongcount/24,其中的55表示55秒,为什么不是一分钟60秒?由于Protues仿真电路图中的MOTOR-ENCODER仿真件存在误差,为了让MOTOR-ENCODER仿真件显示的转速达到数码管显示转速的效果,进行了一定秒数的修改,不过实际运用要以一分钟60秒为准,maichongcount为单片机从外部中断0端口处获取MOTOR-ENCODER仿真件旋转生产的脉冲计数变量,24为MOTOR-ENCODER仿真件每旋转一圈产生的脉冲个数。
    qian=zhuansu/1000;//数码管千位显示
	bai=zhuansu%1000/100;//数码管百位显示
    shi=zhuansu%100/10;//数码管十位显示
    ge=zhuansu%10;//数码管个位显示
    LCD1602DisplaySpeed(qian,bai,shi,ge);//LCD1602显示四位数速度分解函数
    maichongcount=0;//脉冲计数变量归零
    EX0=1;//开外部中断0
   }
  }
  void waibuzhongduanlingjishu() interrupt 0  using 1//外部中断0计数函数,用来计数外部脉冲。
{
   maichongcount++ ;//脉冲计数变量先赋值再自加
  }
  void KeyScan()//按键扫描函数 
{         
   if(qitingjian==0)//判断启停键是否按下
  {
   	Delay(1);//延时
	if(qitingjian==0)//再次判断启停键是否按下
   {
	 Delay(1);//延时
	 while(!qitingjian);//启停键释放
	 qitingnum++;//启停键按下数次变量先赋值再加加
	 qitingled=0;//开启停指示灯
	 if(qitingnum==2)//判断启停键按下数次变量是否为2
	{
	  qitingnum=0;//启停键按下数次变量归零
	  qitingled=1;//关启停指示灯
	  zhengzhuangled=1;//关正转指示灯
	  fanzhuangled=1;//关反转指示灯
	  zhuansuled=1;//关转速指示灯
	  zhengzhuan=1;//关正转端口
	  fanzhuan=1;//关反转端口
	  zhengzhuanflag=0;//正转标志位变量为0
	  fanzhuanflag=0;//反转标志位变量为0
	 }
	}
   }
   if(qitingnum==1)//判断启停键是否第一次按下
  {
  	if(zhengzhuanjian==0)//判断是否按下正转键
   {
   	 Delay(1);//延时
	 if(zhengzhuanjian==0)//再次判断是否按下正转
	{
	  Delay(1);//延时
	  while(!zhengzhuanjian)//正转键释放
      zhengzhuangled=0;//开正转指示灯
	  fanzhuangled=1;//关反转指示灯
	  zhengzhuanflag=1;//正转标志位变量为1
	  fanzhuanflag=0;//反转标志位变量为0
	 }
    }
  	if(fanzhuanjian==0)//判断是否按下反转键
   {
   	 Delay(1);//延时
	 if(fanzhuanjian==0)//再次判断是否按下反转键
	{
	  Delay(1);//延时
	  while(!fanzhuanjian)//反转键释放
	  zhengzhuangled=1;//关正转指示灯
	  fanzhuangled=0;//开反转指示灯
	  zhengzhuanflag=0;//正转标志位变量为0
	  fanzhuanflag=1;//反转标志位变量为1
	 }
    }
   }
   if(zhengzhuanflag==1||fanzhuanflag==1)//判断电机是否为正转或反转
  {
  	if(jiasujian==0)//判断是否按下加速键
   {
   	 Delay(1);//延时
	 if(jiasujian==0)//再次判断是否按下加速键
	{
	  Delay(1);//延时
	  while(!jiasujian);//加速键释放
	  if(pwm<10)//判断脉冲宽度调制变量是否小于10
	 {
	   pwm++;//脉冲宽度调制变量先赋值再加加
	   pwmshi=pwm/10;
	   pwmge=pwm%10;
	  }
	  if(pwm>=10)//判断脉冲宽度调制变量是否大于等于10
	  pwm=10;//脉冲宽度调制变量等于9     
	 }
    }
	if(jiansujian==0)//判断是否按下减速键
   {
   	 Delay(1);//延时
	 if(jiansujian==0)//再次判断是否按下减键
	{
	  Delay(1);//延时
	  while(!jiansujian);//减速键释放
   	  if(pwm>0)//判断脉冲宽度调制变量是否大于0
	 {    
       pwm--;//脉冲宽度调制变量先赋值再减减
	   pwmshi=pwm/10;
	   pwmge=pwm%10;
	  }  
	  if(pwm<=0)//判断脉冲宽度调制变量是否小于等于0
	  pwm=0;//脉冲宽度调制变量等于0 
	 }
    }
   }
  }	    	     
  void main()//主函数
{
//   Timer0Init();//定时器0初始化函数
//   Timer1Init();//定时器1初始化函数
   TimerInit();
   LCD1602Init();//LCD1602初始化函数
   while(1)//
 {
    KeyScan();//键盘扫描函数     
  }
 }  

三、基于51单片机脉冲宽度调制(即PWM)直流电机转速快慢以及霍尔测速的项目Proteus软件仿真单片机外围电路

在这里插入图片描述

四、基于51单片机脉冲宽度调制(即PWM)直流电机转速快慢以及霍尔测速的项目的操作功能描述

(1)、不修改时分秒个位十位数值,只是启动或停止计时的情况:如果第一次按下移位键,则停止计时,启动计时可按下移位键7次或按下启停键,再次停止计时可按下移位键或按下启停键,再次启动计时可按下移位键7次或按下启停键,周而复始。如果第一次按下启停键,则停止计时,启动计时可按下移位键7次或按下启停键,再次停止计时可按下移位键或按下启停键,再次启动计时可按下移位键7次或按下启停键,周而复始。

(2)、修改时分秒个位十位数值,再启动或停止计时的情况:要修改时分秒个位十位上的数值,再启动或停止计时,首先必须按下移位键,此时停止计时,通过点击移位键让LCD1602字符型液晶显示器光标移到时分秒相应个位十位数值上,利用点击增加键或减少键修改数值,修改完毕后,启动计时可按下移位键多次让LCD1602字符型液晶显示器光标回到秒钟个位数值上或按下启停键,停止计时可按下移位键或按下启停键,当按下移位键时,又可修改时分秒个位十位上的数值,再次启动计时可按下移位键多次让LCD1602字符型液晶显示器光标回到秒钟个位数值上或按下启停键,周而复始。

2019-07-04 18:09:37 weixin_43241517 阅读数 620
  • 《计算机体系结构 硬件篇2》之 计算机通信

    第一阶段:《计算机体系结构》课程 分成4篇:分别是硬件篇,软件篇,网络篇,行业篇 第二阶段:《嵌入式技术》课程 分成5部分:Linux基础,C语言基础,C语言,Linux系统编程/网络编程,8051单片机,单片机,linux嵌入式,安卓,项目

    4200 人正在学习 去看看 张先凤

单片机 PWM 占空比计算D=(256-CCAPnH)/ 256(8位PWM模式)

2013-09-18 10:26:20 a602411570 阅读数 1011
  • 《计算机体系结构 硬件篇2》之 计算机通信

    第一阶段:《计算机体系结构》课程 分成4篇:分别是硬件篇,软件篇,网络篇,行业篇 第二阶段:《嵌入式技术》课程 分成5部分:Linux基础,C语言基础,C语言,Linux系统编程/网络编程,8051单片机,单片机,linux嵌入式,安卓,项目

    4200 人正在学习 去看看 张先凤
STC12C5410AD单片机波特率计算工具STC12C5410AD单片机波特率计算工具点击打开链接
2015-06-02 21:28:33 qq_23674297 阅读数 1011
  • 《计算机体系结构 硬件篇2》之 计算机通信

    第一阶段:《计算机体系结构》课程 分成4篇:分别是硬件篇,软件篇,网络篇,行业篇 第二阶段:《嵌入式技术》课程 分成5部分:Linux基础,C语言基础,C语言,Linux系统编程/网络编程,8051单片机,单片机,linux嵌入式,安卓,项目

    4200 人正在学习 去看看 张先凤


单片机——简单计算器
       
         最近喜欢看单片机,用C写了个简单的计算器,LCD作为
显示。在proteus上仿真和开发板上都可以运行。

         源代码及电路图: http://pan.baidu.com/s/1c0pmG0o


图片


图片
 


/*
  一个简单的整数计算器。

  从4*4矩阵键盘接收 0~9 组成的数字,做加减乘除运算,
  并将输入的键值和运算结果显示在LCD上。运算有效位好像只有6位。
*/

#include <reg51.h>   // 51系列单片机头文件
#include <math.h>
#include "LCD.h"       //  引入 自己编写的 LCD 文件

#define uchar unsigned char
#define uint unsigned int 

#define KEY P1  //   键盘 接口
#define LED P0  //   7段数码管用于显示 按下键盘的键值

sbit BEEP = P3^7; //  蜂鸣
uchar code DSY_CODE[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0x00}; //  0~f
uchar  fuhao = '=', k = 0, KeyNO;    //  fuhao  用于 记录 最近 使用的 运算符
unsigned long  num, a[12], temp1 = 0, temp2 = 0;   //  temp1 记录 当前输入的数值  temp2 记录 上次输入的数值

 //===================================================================//

void delay_ms(uint x); //  微秒级延时函数
void Keys_Scan();       //  接收 从键盘输入的 键值
void Beep();     //   响铃函数
void fenjie (unsigned long n);  //  LCD数字显示函数
void LCD_Show();//  LCD键值显示函数


 //================== 调试主函数 ================================//
void main()
{
KEY = 0xf0;
LED = 0xff;  //  数码管 灭

IE=0X81;
TCON=0X01;  //  TCON=0X00;     电平触发时,会接收两次 ??

Init();
W_CMD(0x80+16);

while(1); //  主程序 停留   等待中断
}

//============= 微秒级延时函数 =======================//

void delay_ms(uint x)
{
uchar t;
while(x--)
{
for(t=0;t<120;t++);
}
}

//================== 键值接收函数 ================================//

void Keys_Scan()     //  接收 从键盘输入的 键值
{
KEY = 0xf0;    //  列置0,求行 
if(KEY!=0xf0)    //  若 有键按下
{
delay_ms(10);
if(KEY!=0xf0)  //  确认 是否键按下
{
switch(KEY)   //   求出 所在行 KeyN0按下,松开时显示
{
case 0xe0: KeyNO = 0; break;
case 0xd0: KeyNO = 4; break;
case 0xb0: KeyNO = 8; break;
case 0x70: KeyNO = 12; break;
 

KEY = 0x0f;     //  行置0,求列
switch(KEY)
{
case 0x0e: KeyNO = KeyNO + 0; break;
case 0x0d: KeyNO = KeyNO + 1; break;
case 0x0b: KeyNO = KeyNO + 2; break;
case 0x07: KeyNO = KeyNO + 3;break;
}
while(KEY!=0x0f) ;
}
}
}

//================== 响铃函数 ================================//
void Beep()  //   响铃函数
{
uchar i;
for(i=0;i<100;i++)
{
delay_ms(1);;
BEEP = ~BEEP;
}
BEEP = 1;
}

 //================== LCD数字显示函数 ================================//

void fenjie (unsigned long n) //  接收一个数,求出长度 k , 将其逐位分解,并存放在数组 a【】中  然后输出在 LCD上
{
unsigned long  t = n;
uchar  i = 1;
k = 0;
while(t >= 1)   //  计算 n 的位数  k
{
t = t / 10;
k++;
}

for(i=0;i<k-1;i++)
{
a[i]=0;  

while(n >= pow(10,k-i-1))  
a[i]++;  
n = n - pow(10,k-i-1); 
}
}

a[i]=n;
for(i=0;i<k;i++)
{
num = 48 + a[i];
W_DATA(num);
}
}

//================== LCD键值显示函数 ================================//

void LCD_Show()
{
if(KeyNO < 10)
{
num = 48 + KeyNO;
W_DATA(num);

a[k++] = KeyNO;
if(k==1) 
{
temp1 = a[0];
}
else
{
temp1 = temp1 * 10 + a[k-1];  //  记录 第一个 计算的数字
}
}

else if(KeyNO==10)     //  =
num = 61;
W_DATA(num);

if(fuhao == '=')
{
fenjie(temp1);
}
if(fuhao == '+')
{
temp1 = temp1 + temp2;
fenjie(temp1);
}
if(fuhao == '-')
{
temp1 = temp2 - temp1;
fenjie(temp1);
}
if(fuhao == '*')
{
temp1 = temp1 * temp2;
fenjie(temp1);
}
if(fuhao == '/')
{
temp1 = temp2 / temp1  ;
fenjie(temp1);
}

fuhao = '='; 
}  
   
else if(KeyNO==12)    //  +++++
fuhao = '+';
k = 0;

num = 43;  
W_DATA(num); 
temp2 = temp1;
}  
else if(KeyNO==13)   //  ----- 
fuhao = '-';
k = 0;

num = 45;  
W_DATA(num);
temp2 = temp1;  
}
else if(KeyNO==14)   //  *****
fuhao = '*';
k = 0;

num = 42;  
W_DATA(num); 

temp2 = temp1; 
}
else if(KeyNO==15)  //    ////
fuhao = '/';
k = 0;

num = 47;  
W_DATA(num); 

temp2 = temp1;
}

else if(KeyNO==11)  
{
W_CMD(0x01);   //  清屏
Init();
W_CMD(0x80+16);

temp1 = 0;
k = 0;
fuhao = '=';
}
}



 //=================== 外部中断0 =============================//


void Int0 ()  interrupt 0//  外部中断0  负 跳沿触发
{
Keys_Scan();
LED = DSY_CODE[KeyNO];
Beep();
LCD_Show(); 
KEY = 0xf0;
}
 

2019-01-28 09:34:13 weixin_43858743 阅读数 2057
  • 《计算机体系结构 硬件篇2》之 计算机通信

    第一阶段:《计算机体系结构》课程 分成4篇:分别是硬件篇,软件篇,网络篇,行业篇 第二阶段:《嵌入式技术》课程 分成5部分:Linux基础,C语言基础,C语言,Linux系统编程/网络编程,8051单片机,单片机,linux嵌入式,安卓,项目

    4200 人正在学习 去看看 张先凤

前言

我使用51,STC这一类的单片机做控制好几年,一直是使用现成的程序,在其上修修改改,以达到需求动作目的即可。从来都是不求甚解。想法既是如此,会用即可,了解那么多做什么。
此次又在做一项目,里面用到I2C通讯。本来是直接复制粘贴了事,却没想对里面的一个小小的延时函数起了兴趣,由于本人是基础功底只有5战斗力的渣渣,写写画画了一天才搞了个大体明白。
以前总是在看其他博主的文章,默默潜水。然而此次,突然就忍不住想写篇文章发表一下费尽心思的微不足道的心得体会。

正文

void Delay10us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	_nop_();
	i = 27;
	while (--i);
}

上面这段代码是用STC-ISP软件中的软件延时计算器给出的,选用的是8051指令集STC-Y5,延时10us。
以前都是直接这么拿来用的,今天却突然想搞个明白,为什么代码要这么写。

于是查了各方资料。
从单片机计时的源头找起,它由下面几部分依次组成。

首先是时钟周期的算法:时钟周期(T)=1(秒)/晶振频率。

(比如:上面代码的时钟周期为1/12M(秒))。
这是单片机的基本时间单位。是由晶振震荡出来的,也叫震荡周期。

其次是机器周期:机器周期是由时钟周期组成的,机器周期是单片机完成一个基本操作所需要的时间。

关于机器周期,每种单片机可能都不太一样,我也只用过传统51和STC这两款,就拿此来对比下

1 传统的8051单片机:

它的1个机器周期是由12个时钟周期组成的。
以12M晶振举例,它的一个机器周期就是:12(个时钟周期)*1(秒)/12MHz = 1(us)

2 STC单片机:

拿我常用的STC12C5A60S2这款单片机来讲,它可以有两个模式选择,
一个是1T模式,在这个模式下STC单片机1个时钟周期就是1个机器周期;
另一个是12T模式,这个模式下STC单片就和传统的8051单片机一样,12个时钟周期组成1个机器周期。
由此可见1T模式的速度就是12T模式的12倍。
以12M晶振为例,1T模式下就可以算得机器周期是:
1(个时钟周期)*1(秒)/12Mhz = 1/12(us)

最后是指令周期:这个是单片机执行一条指令所需要的时间,它是由机器周期组成的。

现在可以回到正文开头的代码中了。这个10us的函数是怎么得出来的呢?
这个我之前查过很多资料,比如执行while语句需要多少个机器周期。赋值需要多少个周期。也就是查这个占用了我很大一部分时间。直到最后将上面的延时函数直接调到main函数中debug调试,才明白,问题其实很简单啊。
无论是执行什么语句,最终都会回到汇编上来,debug里单步调试,所有的指令周期就会明明白白了。
我用main函数直接调用延时函数,如下:

void Delay10us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	_nop_();
	i = 27;
	while (--i);
}
main
{
    Delay10us();
}

我用的keil软件,将上述build之后,点击debug,开始调试
.在这里插入图片描述
看图片上,开始debug,程序的起始就在C:0x0183 020171 LJMP Delay10us(C:0171),
这里有个长转移指令LJMP,它要转移到C:0171行去执行Delay10us这个函数。
那执行LJMP这个指令需要多长时间呢,查找STC数据手册,在1T模式下,此条指令在单片机上运行需要4个时钟周期。
接下来,按单步调试F11键,如下图:
在这里插入图片描述
程序成功转移到C:0171行,跳转到Delay10us函数中,此行程序执行NOP指令,空操作。查STC数据手册,NOP指令占用1个时钟周期。
接下来C:0172行,依然是NOP指令,1个时钟周期。
接下来C:0173行,此行执行 MOV R7,#0x1B,将立即数送入寄存器。是将27赋值给i。依然查手册,此条指令2个时钟周期。
继续:
在这里插入图片描述
此时执行到while语句了,这里执行的指令时 DJNZ R7,C:0175,寄存器减1非0转移。此条指令执行1次4个时钟周期。
上面已经将寄存器填入27了,因此这条指令将执行27次。
继续:
在这里插入图片描述
循环了27次,终于到0了,程序继续向下执行,此行指令RET,子程序返回。此条指令4个时钟周期。
继续:
在这里插入图片描述
程序又回到了起点。
好了,可以计算一下此次延时的时间了。1个LJMP,4时钟;2个NOP,2时钟;1个MOV,2时钟;27个DJNZ,108时钟;1个RET,4时钟。
4+2+2+108+4=120。
单片机的时钟周期是:1(S)/12MHz = 1/12(us)
此次延时的时间是:120 × 1/12(us)= 10(us)

总结

其实并没有绝对的准确延时,上面只是理想化的状态,单片机的中断或者其他事件都可能影响到延时的。
另外,同样的STC单片机,同样的延时10us,同样的1T,官方给出的STC12系列和STC15系列的延时函数就不一样,STC12系列在延时函数内部要少两个NOP指令。debug对比,也是少量NOP,其他都一样。按照12系列和15系列的手册描述,他们的指令周期是相同的。那么他们的差别在哪?单片机内部结构的问题还是官方错误造成的误差?
各位大神若有知晓的望请告知。不胜感激。

51单片机延时计算

阅读数 640

没有更多推荐了,返回首页