C51单片机课程设计报告

内容提要

单片微型计算机简称单片机,是典型的嵌入式微控制器,常用英文字母的缩写MCU表示单片机,它最早是被用在工业控制领域。单片机由芯片内仅有CPU的专用处理器发展而来。最早的设计理念是通过将大量外围设备和CPU集成在一个芯片中,使计算机系统更小,更容易集成进复杂的而对体积要求严格的控制设备当中。

单片机又称单片微控制器,它不是完成某一个逻辑功能的芯片,而是把一个计算机系统集成到一个芯片上。相当于一个微型的计算机,和计算机相比,单片机只缺少了I/O设备。概括的讲:一块芯片就成了一台计算机。它的体积小、质量轻、价格便宜、为学习、应用和开发提供了便利条件。同时,学习使用单片机是了解计算机原理与结构的最佳选择。

单片机是靠程序运行的,并且可以修改。通过不同的程序实现不同的功能,尤其是特殊的独特的一些功能,这是别的器件需要费很大力气才能做到的,有些则是花大力气也很难做到的。一个不是很复杂的功能要是用美国50年代开发的74系列,或者60年代的CD4000系列这些纯硬件来搞定的话,电路一定是一块大PCB板!但是如果要是用美国70年代成功投放市场的系列单片机,结果就会有天壤之别!只因为单片机的通过你编写的程序可以实现高智能,高效率,以及高可靠性!

目前单片机渗透到我们生活的各个领域,几乎很难找到哪个领域没有单片机的踪迹。导弹的导航装置,飞机上各种仪表的控制,计算机的网络通讯与数据传输,工业自动化过程的实时控制和数据处理,广泛使用的各种智能IC卡,民用豪华轿车的安全保障系统,录像机、摄像机、全自动洗衣机的控制,以及程控玩具、电子宠物等等,这些都离不开单片机。更不用说自动控制领域的机器人、智能仪表、医疗器械以及各种智能机械了。

所以说对于单片机课程的学习对于我们电子信息工程专业的学生来说至关重要,然而学习单片机课程设计是其一个极为重要的实践环节,无论是硬件扩展、接口应用还是编程方法、程序调试,都离不开该环节。单片机课程设计过程中,学生通过查阅资料、接口设计、程序设计、安装调试等环节,完成一个涉及MCS-51单片机多种资源应用并具有综合功能的小系统目标板的设计与编程应用。通过该环节,学生不但能够将课堂上学到的理论知识与实际应用结合起来,而且能够对电子电路、电子元器件等方面的知识进一步加深认识,同时在软件编程、排错调试、焊接技术、相关仪器设备的使用技能等方面得到全面的锻炼和提高。

  


目录

一、课程设计目的:.................................................. 5

二、已知技术参数和条件............................................. 5

三、设计任务和要求................................................. 5

四、需求分析....................................................... 6

五、硬件电路图设计及描述........................................... 7

六、软件设计思想及流程............................................ 10

七、实验效果...................................................... 13

八、源程序代码.................................................... 16

九、课程设计体会.................................................. 30

十、参考文献...................................................... 31


一、课程设计目的:

通过课程设计,使学生巩固和加深对单片机基本知识的理解,能够将课堂上学到的理论知识与实际应用结合起来,而且能够对电子电路、电子元器件等方面的知识进一步加深认识,同时在软件编程、排错调试、焊接技术、相关仪器设备的使用技能等方面得到全面的锻炼和提高。学会查寻资料、方案设计、方案比较,以及单元电路设计计算等环节,进一步提高学生综合运用所学知识的能力,提高分析解决实际问题的能力。锻炼分析、解决电子电路问题的实际本领,通过此综合训练,为以后毕业设计打下一定的基础。

二、已知技术参数和条件

(1)STC89C51单片机

(2)LED七段数码显示器 及8个LED小彩灯

(3)74HC595串口转并口芯片

(4)电位器

(5)按键

(6) PCF8591 AD-DA数模转换芯片

(7)电阻箱,5V电源, 电阻若干 ,导线若干

(8)KEIL 软件

(9)MCS-51单片机官方烧写程序软件STC_ISP_V488.exe

三、设计任务和要求

1. 通过单片机的I/O口直接控制8盏LED小灯,实现 7 种彩灯灯光效果(左移、右移、中间向两边展开、两边向中间合拢等,不含全部点亮,全部熄灭);

2.用按键来切换控制器的灯光模式,能显示当前的运行模式号;

3.彩灯效果的移动速度基于AD输入量的大小(控制器通过   PCF8591   对0~5V信号进行采样,根据模拟信号的大小而改变彩灯移动速度);彩灯控制器还要显示当前模拟输入信号的大小(数码管显示);

4.彩灯效果中的延时采用定时中断实现。按键、数码管直接用C51的I/O驱动

扩展:可以通过串口通讯芯片595来控制彩灯效果的切换,将AD采样结果显示的数码管上。

四、需求分析

 根据本次课程设计题目分析及其题目简化,需要做的是通过电位器来控制8盏彩灯的扫描显示速度,并且显示此时AD芯片的采样值,并且通过一个按键来切换不同的彩灯的7种显示模式。于是乎,可以将系统分为两个部分:其一,模拟采样部分;其二,彩灯显示部分。

最后得到以下的设计方案:

1  通过一个电位器来得到一个可以改变的模拟值;

2  采用 PCF8591 采样电位器所产生的模拟值,并将其转化为数字信号;

3  MCU 通过I2C总线读取 PCF8951 内部ROM 中所缓存的数值;

4  编写数码管显示函数,通过单片机的I/O口驱动数码管显示 PCF8591 所采样的值。(数码管高四位显示PCF8591所采样的数值,低四位显示当前PCF8591所采取的精确电压值,精确到小数点后三位);

5  编写LED彩灯的显示,利用定时器0 来控制每一盏小灯的显示延迟时间,利用定时器1和 pcf8591所采样的数字信号来共同确定LED彩灯的扫描速度,即通过调节电位器可以改变LED灯的扫描速度(通过MCU的I/O驱动74HC595芯片实现串口转并口来驱动8个LED小彩灯的显示);

 6  采用外部中断0 来切换LED彩灯的显示模式。

五、硬件电路图设计及描述

 

 

注:电路图原图另有PDF文件

1、数码管显示电路设计

课程设计中所用的数码管为共阴极数码管,当给其引脚加入高电平时,数码管中对应的那一段就亮。下图为外部显示图,其内部结构如下:

共阴极数码管内部结构

2、LED显示电路设计

本次课程设计中要用发光二极管显示状态。由下图知发光二极管阳极经过电阻接高电平,当其阴极为低电平时,二极管就发光显示所处状态。通过串口转并口芯片74HC595的并口输出来改变LED灯的显示状态。

3、AD转换电路设计

      此次课程设计的AD转换芯片采用的是PCF8591芯片,芯片的9、10 管脚是I2C串口协议传输引脚,其与MCU的P2^0、P2^1接口连接。A0、A1、A2为地址线,此处接地,地址为I2C传输数据的地址为000。四路模拟输入接口为AIN0、AIN1、AIN2、AIN3,此处只选择一路模拟输入-------AIN0。模拟输入信号通过一个电位器的改变而提供。

 

4、时钟电路设计

时钟电路对单片机是不可缺的,单片机的每个功能都要以时钟电路为基础工作。单片机内部自带一个时钟电路,外部接入定时控制元件即可构成一个稳定的自激振荡器。其中机器周期共有12个振荡脉冲周期,因此,机器周期是时钟周期的12倍。本课程设计中时钟电路中使用的晶体是12MHz,则时钟周期为(1/12)us,机器周期为1us。实验图如下:

5、外部中断信号电路设计

     外部中断信号的是由一个简单的不带锁的按键开关提供的一个脉冲信号。

六、软件设计思想及流程

(1)使用单片机资源的情况:

设计时使用单片机资源的情况如下:AT89S51单片机的P0、P1口作为数码管数据线接口,其中,P0口接是段选,P1接位选,P2^0、P2^1接AD转换芯片PCF8591的I2C串口接口,P3^4、P3^5、P3^6 口接74HC595的串口输入接口,P3^2外接外部中断按键。单片机定时器0、定时器1均匀方式1来控制定时器运行。

(2)软件系统的各个模块功能:

本设计的软件系统主要采用以下基本模块来实现:

主程序、中断服务程序、串口传输程序。

主程序用于对于各个程序模块的运行及控制,以及各个模式程序的初始化。特别是定时中断的初始值。在主程序中还主要的对pcf8591进行了操作,采样,量化,显示。

有三个中断服务程序,分别是定时器0中断、定时器1中断、外部中断,通过外部中断来实现各个LED灯的显示模式的切换。定时器中断0用于控制LED灯的显示延时。定时器1用于操作LED灯的不同模式的显示。

串口传输程序只要有两种串口传输函数,分别I2C串口传输函数和595串口转并口的串口传输函数。在串口函数里面详细的对串口进行了操作,里面有I2C串口的初始化,终止,数据传输,应答,595串口的传输。

(3)设计的软件流程框图:

 


 


(4)软件调试过程

使用keil软件在做写程序之前选择create HEX Fi:表示生成可烧写进单片机的HEX文件。

在编写程序结束之后点击编译,生成HEX文件。

再生成HEX文件之后打开STC单片机官方烧写软件进行程序烧写:1选择单片机型号;2选择生成的HEX文件;3选择单片机连接的COM口;4选择传输的波特率;5断电烧写。

七、实验效果

在完成烧写程序完成之后单片机即可正常执行程序。执行效果如下:

电位器1即模拟信号的输入,通过旋转电位器1 可改变数码管的显示电位器1的采样值,与调节LED的扫描显示速度。通过按键2可改变LED灯的显示模式。不同的实验效果图还有如下:

八、源程序代码

/************   main.c  ***************/

#include<reg52.h>

#include <I2C.H>

#include <intrins.H>

#define  PCF8591 0x90    //PCF8591 地址

unsigned char LED[9]={0x01,0x01,0x01,0x01,0x7F,0x81,0x81,0x01,0xAA};//LED的显示模式

unsigned char LED_kind=1,a=0x80,b=0x01;

unsigned int j,mun;

//此表为 LED 的字模, 共阴数码管 0-9  -

unsigned char code Disp_Tab[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f

,0x6f};

unsigned long xdata  LedOut[8];

unsigned int  D[3],n=1;

   

/********************DAC 变换, 转化函数 *********************/

bit DACconversion(unsigned char sla,unsigned char c,  unsigned char Val)

{

   Start_I2c();              //启动总线

   SendByte(sla);            //发送器件地址

   if(ack==0)return(0);

   SendByte(c);              //发送控制字节

   if(ack==0)return(0);

   SendByte(Val);            //发送DAC的数值 

   if(ack==0)return(0);

   Stop_I2c();               //结束总线

   return(1);

}

/***********************ADC发送字节[命令]数据函数***************************/

bit ISendByte(unsigned char sla,unsigned char c)

{

   Start_I2c();              //启动总线

   SendByte(sla);            //发送器件地址

   if(ack==0)return(0);

   SendByte(c);              //发送数据

   if(ack==0)return(0);

   Stop_I2c();               //结束总线

   return(1);

}

/**************************ADC读字节数据函数**********************/

unsigned char IRcvByte(unsigned char sla)

{  unsigned char c;

   Start_I2c();          //启动总线

   SendByte(sla+1);      //发送器件地址+1使写入地址为0x91   为读地址1001 0001

   if(ack==0)return(0);

   c=RcvByte();          //读取数据0

   Ack_I2c(1);           //发送非就答位

   Stop_I2c();           //结束总线

   return(c);

}

//****************************************************/

main()

{  char i;

    TMOD= 0x11;    //设置计数模式为方式2

    TH0=(65536-500)/256;  

    TL0=(65536-500)%256;

    TH1=(65536-50)/256;  

    TL1=(65536-50)%256;

    IE =0x8B;      //开定时器0、定时器1

    TR0= 1;

    TR1=1;

    IT0=1;

 while(1)

 {    

             ISendByte(PCF8591,0x40);           //使用通道0进行采样

             D[0]=IRcvByte(PCF8591);            //从pcf8591读取模数转换的数值

             D[1]=D[0]*5/0.255;    //将模数转换的数值转换成电压值 

             D[2]=D[0]*2/25.5;     //用于控制LED的速度

 /***********将AD的值送到LED数码管显示*************/

           

     LedOut[0]=Disp_Tab[D[0]%10000/1000];

     LedOut[1]=Disp_Tab[D[0]%1000/100];

     LedOut[2]=Disp_Tab[D[0]%100/10];

     LedOut[3]=Disp_Tab[D[0]%10];

     

 /***********转换后的电压值送到LED数码管显示************/

     LedOut[4]=Disp_Tab[D[1]%10000/1000]|0x80;

     LedOut[5]=Disp_Tab[D[1]%1000/100];

     LedOut[6]=Disp_Tab[D[1]%100/10];

     LedOut[7]=Disp_Tab[D[1]%10]; 

     

     for( i=0; i<8; i++)

     {  P0 = LedOut[i];     //段选择

           

      switch(i)   //使用switch 位选择                

         {     

            case 0:P1=0xFE;break;        

            case 1:P1=0xFD;break;              

            case 2:P1=0xFB;break;

            case 3:P1=0xF7;break;

            case 4:P1=0xEF;break;

            case 5:P1=0xDF;break;

            case 6:P1=0xBF;break;

            case 7:P1=0x7F;break;

         }

        //   for (j = 0 ; j<90 ;j++) { ;}      //扫描间隔时间

          n=1;

          while(n);

              P1=0xFF;                     //清除余辉相映

      }

 } 

}

void Timer0(void) interrupt 1      //3定时器1的中断号  1定时器0的中断号 0外部中断1 2外部中断2  4串口中断

{

    TH0=(65536-1000)/256;  

    TL0=(65536-1000)%256;  

     n=0;

}

void Timer2(void) interrupt 0

{

   if(LED_kind<=7)

   LED_kind++;

   else

   LED_kind=1;

void Timer1(void) interrupt 3     //3定时器1的中断号  1定时器0的中断号 0外部中断1 2外部中断2  4串口中断

{

     unsigned char c=1,d=0;

     j++;

    TH1=(65536-40000)/256;  

    TL1=(65536-40000)%256;

    if(j>D[2])

    {  

     

        switch(LED_kind)

        {

           case 1:  HC595SendData(~(LED[0]++));break;

           case 2: LED[1]=_cror_(LED[1], 1);mun=~LED[1];    HC595SendData(mun);break;

           case 3: LED[2]=_crol_(LED[2], 1);

                    mun=~LED[2];

                    HC595SendData(mun);

                    break;

           case 4: {

                        mun=~LED[3];

                        if(LED[3]<=0x80)

                        {  

                            HC595SendData(mun);

                            LED[3]=LED[3]<<1;

                            LED[3]=LED[3]|0x01;

                        }

                        else

                        {

                            LED[3]=0x01;

                            HC595SendData(mun);

                        }

     

                    }

                   break;

            case 5:{

                       HC595SendData(LED[4]);

                        LED[4]=LED[4]>>1;

                        if(LED[4]==0)

                        {

                          HC595SendData(LED[4]);

                          LED[4]=0x7F;

                         }

                    }

                 break;

             case 6:{

                     if(a!=0)

                          {

                              d=a^LED[7];

                              HC595SendData(~d);

                              a=a>>1;

                              LED[7]=LED[7]<<1;

                         }

                       else

                         {

                            a=0x80;

                            LED[7]=0x01;

                            d=a|LED[7];

                           HC595SendData(~d);

                          } 

                     }break;

                     

             case 7:{

                      LED[8]=~LED[8];

                      HC595SendData(LED[8]);

                     }break;

        }                                         

        j=0;

    }

}

/***************************  I2C.c ************************/

#include<reg52.h>

#include <intrins.h>

#include <I2C.H>

#define uchar unsigned char

#define uint  unsigned int

#define  NOP()   _nop_()   /* 定义空指令 */

#define  _Nop()  _nop_()   /*定义空指令*/

sbit     SCL=P2^1;       //I2C  时钟

sbit     SDA=P2^0;       //I2C  数据

bit ack;                 /*应答标志位*/

   

sbit MOSIO =P3^4;          //HC595数据线

sbit R_CLK =P3^5;          //输出时钟

sbit S_CLK =P3^6;         //输入时钟

/*******************************************************************

                     起动总线函数              

函数原型: void  Start_I2c(); 

功能:     启动I2C总线,即发送I2C起始条件. 

********************************************************************/

void Start_I2c()

{

  SDA=1;         /*发送起始条件的数据信号*/

  _Nop();

  SCL=1;

  _Nop();        /*起始条件建立时间大于4.7us,延时*/

  _Nop();

  _Nop();

  _Nop();

  _Nop();   

  SDA=0;         /*发送起始信号*/

  _Nop();        /* 起始条件锁定时间大于4μs*/

  _Nop();

  _Nop();

  _Nop();

  _Nop();      

  SCL=0;       /*钳住I2C总线,准备发送或接收数据 */

  _Nop();

  _Nop();

}

/*******************************************************************

                      结束总线函数              

函数原型: void  Stop_I2c(); 

功能:     结束I2C总线,即发送I2C结束条件. 

********************************************************************/

void Stop_I2c()

{

  SDA=0;      /*发送结束条件的数据信号*/

  _Nop();       /*发送结束条件的时钟信号*/

  SCL=1;      /*结束条件建立时间大于4μs*/

  _Nop();

  _Nop();

  _Nop();

  _Nop();

  _Nop();

  SDA=1;      /*发送I2C总线结束信号*/

  _Nop();

  _Nop();

  _Nop();

  _Nop();

}

/*******************************************************************

                 字节数据发送函数              

函数原型: void  SendByte(UCHAR c);

功能:     将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对

          此状态位进行操作.(不应答或非应答都使ack=0)    

           发送数据正常,ack=1; ack=0表示被控器无应答或损坏。

********************************************************************/

void  SendByte(unsigned char  c)

{

 unsigned char  BitCnt;

 for(BitCnt=0;BitCnt<8;BitCnt++)  /*要传送的数据长度为8位*/

    {

     if((c<<BitCnt)&0x80)SDA=1;   /*判断发送位*/

       else  SDA=0;               

     _Nop();

     SCL=1;               /*置时钟线为高,通知被控器开始接收数据位*/

      _Nop();

      _Nop();             /*保证时钟高电平周期大于4μs*/

      _Nop();

      _Nop();

      _Nop();        

     SCL=0;

    }

   

    _Nop();

    _Nop();

    SDA=1;                /*8位发送完后释放数据线,准备接收应答位*/

    _Nop();

    _Nop();  

    SCL=1;

    _Nop();

    _Nop();

    _Nop();

    if(SDA==1)ack=0;    

       else ack=1;        /*判断是否接收到应答信号*/

    SCL=0;

    _Nop();

    _Nop();

}

/*******************************************************************

                 字节数据接收函数              

函数原型: UCHAR  RcvByte();

功能:        用来接收从器件传来的数据,并判断总线错误(不发应答信号),

          发完后请用应答函数应答从机。 

********************************************************************/   

unsigned char   RcvByte()

{

  unsigned char  retc;

  unsigned char  BitCnt;

 

  retc=0;

  SDA=1;                     /*置数据线为输入方式*/

  for(BitCnt=0;BitCnt<8;BitCnt++)

      {

        _Nop();          

        SCL=0;                  /*置时钟线为低,准备接收数据位*/

        _Nop();

        _Nop();                 /*时钟低电平周期大于4.7μs*/

        _Nop();

        _Nop();

        _Nop();

        SCL=1;                  /*置时钟线为高使数据线上数据有效*/

        _Nop();

        _Nop();

        retc=retc<<1;

        if(SDA==1)retc=retc+1;  /*读数据位,接收的数据位放入retc中 */

        _Nop();

        _Nop();

      }

  SCL=0;   

  _Nop();

  _Nop();

  return(retc);

}

/********************************************************************

                     应答子函数

函数原型:  void Ack_I2c(bit a);

功能:      主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)

********************************************************************/

void Ack_I2c(bit a)

{

 

  if(a==0)SDA=0;              /*在此发出应答或非应答信号 */

  else SDA=1;

  _Nop();

  _Nop();

  _Nop();     

  SCL=1;

  _Nop();

  _Nop();                    /*时钟低电平周期大于4μs*/

  _Nop();

  _Nop();

  _Nop(); 

  SCL=0;                     /*清时钟线,钳住I2C总线以便继续接收*/

  _Nop();

  _Nop();   

}

/********************************************************************

                     74HC595串口传输函数

函数原型:  void HC595SendData(unsigned char SendVal);

功能:    将串行信号转换成并行信号  ********************************************************************/

void HC595SendData(unsigned char SendVal)

  unsigned char i;

       

  for(i=0;i<8;i++)

   {

    if((SendVal<<i)&0x80) MOSIO=1; //set dataline high  0X80  最高位与SendVal左移的最高位 进行逻辑运算

    else MOSIO=0;                  // 如果为真 MOSIO = 1 

    S_CLK=0;

    NOP();

    NOP();

    S_CLK=1;   

   }

  

   

  R_CLK=0; //set dataline low

  NOP();

  NOP();

  R_CLK=1; //片选

}


九、课程设计体会

课程设计是培养学生综合运用所学知识,发现、提出、分析和解决实际问题,锻炼实践能力的重要环节,是对学生实际工作能力的具体训练和考察过程,对我们学工科的同学来说尤为重要。本次课程设计,是基于单片机的最小系统设计,我设计的是AD转换+中断应用+LED显示,且适合较小的电压。

回顾起此次电子课程设计,至今我仍感慨颇多。的确,从理论到实践,在整整两星期的日子里,可以说得上是苦多于甜,但是我从中学到了很多很多的东西。我不仅巩固了以前所学过的知识,而且学到了很多在书本上没有的知识,提高了自己的实际动手能力和独立思考的能力。我知道只拥有理论知识是远远不够的,而这次工艺实习给了我很好的一个机会,让我真正领教了这实践过程中的困难重重和心理变化,比如说AD转换芯片不懂怎么去选,微处理器软件设计掌握得不好……专业技术知识的欠缺让我望而却步,我告诉自己我能做到,于是我鼓起勇气重新拿起课本,并在老师的悉心指导下一步步有了转机。成功的那一刻那种喜悦是不言而喻的,那种自信是发自内心的最深处。

这次课程设计终于顺利完成了,在设计的过程中可以说是困难重重,这毕竟第一次做的,难免会遇到过各种各样的问题,感谢张老师对我的耐心讲解和悉心指导。同时,在老师的身上我也学到了很多的实用知识,对我以后的专业发展奠定了一定的基础。在设计的过程中发现了自己的不足之处,对以前所学过的知识理解得不够深刻,掌握得不够牢固,通过这次课程设计之后,一定把以前所学过的知识重新温故。这次的设计让我有了信心,其实没有想象中那么难,只要肯去做,就一定可以做到!期待我的毕业设计,我会更加投入付出,给老师一份满意的答卷,也为我的大学画上圆满的句号。


十、参考文献

[1] 冯文旭 《单片机原理及应用》 机械工业出版社

[2] 苏家健、曹柏荣、汪志锋 《单片机原理及应用技术 作》 高等教育出版社

[3]杜洋 《爱上单片机》  人民邮电出版社,2010.

[4] 王守中《 51单片机开发入门与典型实例》 北京:人民邮电出版社.

[5]谭浩强 《C程序设计》 北京:清华大学出版社,2005.

相关推荐