EDN首页   博客首页

最新日志

发表于:2008-9-16 13:03:17
标签:无标签

1

FPGA配置(AS、PS、JTAG)

2007-12-06 10:41
FPGA配置
作者:长江学者 2007-11-23 09:51:29
标签: 我记录我的校园知识/探索
  

很多兄弟对于CPLD下JTAG的下载很熟悉了,可转到FPGA来的时候,多多少少有些迷惑,怎么出现配置芯片了,为什么要用不同的下载电缆,不同的下载模式?我就自己知道的一点东西谈一些个人的见解,并发一些资料.希望路过的朋友喝个采,版主给点威望.有问题大家也一起讨论,欢迎拍砖.

1.FPGA器件有三类配置下载方式:主动配置方式(AS)和被动配置方式(PS)和最常用的(JTAG)配置方式。
AS由FPGA器件引导配置操作过程,它控制着外部存储器和初始化过程,EPCS系列.如EPCS1,EPCS4配置器件专供AS模式,目前只支持 Cyclone系列。使用Altera串行配置器件来完成。Cyclone期间处于主动地位,配置期间处于从属地位。配置数据通过DATA0引脚送入 FPGA。配置数据被同步在DCLK输入上,1个时钟周期传送1位数据。(见附图)

PS则由外部计算机或控制器控制配置过程。通过加强型配置器件(EPC16,EPC8,EPC4)等配置器件来完成,在PS配置期间,配置数据从外部储存部件,通过DATA0引脚送入FPGA。配置数据在DCLK上升沿锁存,1个时钟周期传送1位数据。(见附图)

JTAG接口是一个业界标准,主要用于芯片测试等功能,使用IEEE Std 1149.1联合边界扫描接口引脚,支持JAM STAPL标准,可以使用Altera下载电缆或主控器来完成。

FPGA在正常工作时,它的配置数据存储在SRAM中,加电时须重新下载。在实验系统中,通常用计算机或控制器进行调试,因此可以使用PS。在实用系统中,多数情况下必须由FPGA主动引导配置操作过程,这时FPGA将主动从外围专用存储芯片中获得配置数据,而此芯片中fpga配置信息是用普通编程器将设计所得的pof格式的文件烧录进去。
专用配置器件:epc型号的存储器
常用配置器件:epc2,epc1,epc4,epc8,epc1441(现在好象已经被逐步淘汰了)等
对于cyclone cycloneII系列器件,ALTERA还提供了针对AS方式的配置器件,EPCS系列.如EPCS1,EPCS4配置器件也是串行配置的.注意,他们只适用于cyclone系列.
除了AS和PS等单BIT配置外,现在的一些器件已经支持PPS,FPS等一些并行配置方式,提升配置了配置速度。当然所外挂的电路也和PS有一些区别。还有处理器配置比如JRUNNER 等等,如果需要再baidu吧,至少不下十种。比如Altera公司的配置方式主要有Passive Serial(PS),Active Serial(AS),Fast Passive Parallel(FPP),Passive Parallel Synchronous(PPS),Passive Parallel Asynchronous(PPA),Passive Serial Asynchronous(PSA),JTAG等七种配置方式,其中Cyclone支持的配置方式有PS,AS,JTAG三种.

2 对FPGA芯片的配置中,可以采用AS模式的方法,如果采用EPCS的芯片,通过一条下载线进行烧写的话,那么开始的"nCONFIG,nSTATUS"应该上拉,要是考虑多种配置模式,可以采用跳线设计。让配置方式在跳线中切换,上拉电阻的阻值可以采用10K
3,在PS模式下tip:如果你用电缆线配置板上的FPGA芯片,而这个FPGA芯片已经有配置芯片在板上,那你就必须隔离缆线与配置芯片的信号.(祥见下图).一般平时调试时不会把配置芯片焊上的,这时候用缆线下载程序.只有在调试完成以后,才把程序烧在配置芯片中, 然后将芯片焊上.或者配置芯片就是可以方便取下焊上的那种.这样出了问题还可以方便地调试.
&<60; 在AS模式下tip: 用过一块板子用的AS下载,配置芯片一直是焊在板子上的,原来AS方式在用线缆对配置芯片进行下载的时候,会自动禁止对FPGA的配置,而PS方式需要电路上隔离。

4,一般是用jtag配置epc2和flex10k,然后 epc2用ps方式配置flex10k.这样用比较好.(这是我在网上看到的,可以这样用吗?怀疑中)望达人告知.
5,下载电缆,Altera下的下载电缆分为byteblaster和byteblasterMV,以及ByteBlaster II,现在还推出了基于USB-blaster.由于BB基本已经很少有人使用,而USB-Blaster现在又过于昂贵,这里就说一下BBII和 BBMV的区别.
BBII支持多电压供电5.5v,3.3v,2.5v,1.8v;
BBII支持三种下载模式:AS,可对Altera的As串行配置芯片(EPCS系列)进行编程
&<60; &<60; &<60; &<60; &<60; &<60; &<60; &<60; &<60; &<60; PS,可对FPGA进行配置
&<60; &<60; &<60; &<60; &<60; &<60; &<60; &<60; &<60; JTAG,可对FPGA,CPLD,即Altera配置芯片(EPC系列)编程
&<60; &<60; 而BBMV只支持PS和JTAG
6,一般在做FPGA实验板,(如cyclone系列)的时候,用AS+JTAG方式,这样可以用JTAG方式调试,而最后程序已经调试无误了后,再用 AS模式把程序烧到配置芯片里去,而且这样有一个明显的优点,就是在AS模式不能下载的时候,可以利用Quartus自带的工具生成JTAG模式下可以利用的jic文件来验证配置芯片是否已经损坏,方法祥见附件(这是骏龙的人写的,摘自咱们的坛子,如有版权问题,包涵包涵).
7.Altera的FPGA可以通过单片机,CPLD等加以配置,主要原理是满足datasheet中的时序即可,这里我就不多说了,有兴趣的朋友可以看看下面几篇文章,应该就能够明白是怎么回事了.
8.配置时,quartus软件操作部分:
(1).assignment-->device-->device&pin options-->选择configuration scheme,configuaration mode,configuration device,注

意在不支持远程和本地更新的机器中configuration mode不可选择,而configuration device中会根据不同的配置芯片产生pof文件,

如果选择自动,会选择最小密度的器件和适合设计
(2).可以定义双口引脚在配置完毕后的作用,在刚才的device&pin option-->dual-purpose pins-->,可以在配置完毕后继续当I/O口

使用
(3).在general菜单下也有很多可钩选项,默认情况下一般不做改动,具体用法参见altera configuration

handbook,volume2,sectionII.
(4)关于不同后缀名的文件的适用范围:
sof(SRAM Object File)当直接用PS模式下将配置数据下到FPGA里用到,USB BLASTER,MASTERBLASER,BBII,BBMV适用,quartusII会自

动生成,所有其他的配置文件都是由sof生成的.
pof(Programmer Object File)也是由quartusII自动生成的,BBII适用,AS模式下将配置数据下到配置芯片中
rbf(Raw Binary File)用于微处理器的二进制文件.在PS,FPP,PPS,PPA配置下有用处
rpd(Raw Programing Data File)包含bitstream的二进制文件,可用AS模式配置,只能由pof文件生成
hex(hexadecimal file)这个就不多说了,单片机里很多
ttf(Tabular Text File)适用于FPP,PPS,PPA,和bit-wide PS配置方式
sbf(Serial Bitstream File)用PS模式配置Flex 10k和Flex6000的
jam(Jam File)专门用于program,verigy,blank-check
jic的用法6楼已经提到,在这里就不多说了

原文链接
http://blog.sina.com.cn/s/reader_437a2a7f01000eu7.html

点击此处查看原文 >>

系统分类: CPLD/FPGA   |    用户分类:    |    来源: 转贴

评论(1) | 阅读(102)
发表于:2008-8-28 17:18:40
标签:无标签

1

看看这个关于AVR单片机的镕丝位配置的文章



作者:马潮老师


正确配置AVR熔丝位

对AVR熔丝位的配置是比较细致的工作,用户往往忽视其重要性,或感到不易掌握。下面给出对AVR熔丝位的配置操作时的一些要点和需要注意的相关事项。

(1)在AVR的器件手册中,对熔丝位使用已编程(Programmed)和未编程(Unprogrammed)定义熔丝位的状态,“Unprogrammed”表示熔丝状态为“1”(禁止);“Programmed”表示熔丝状态为“0”(允许)。因此,配置熔丝位的过程实际上是“配置熔丝位成为未编程状态“1”或成为已编程状态“0””。

(2)在使用通过选择打钩“√”方式确定熔丝位状态值的编程工具软件时,请首先仔细阅读软件的使用说明,弄清楚“√”表示设置熔丝位状态为“0”还是为“1”。

(3)使用CVAVR中的编程下载程序时应特别注意,由于CVAVR编程下载界面初始打开时,大部分熔丝位的初始状态定义为“1”,因此不要使用其编程菜单选项中的“all”选项。此时的“all”选项会以熔丝位的初始状态定义来配置芯片的熔丝位,而实际上其往往并不是用户所需要的配置结果。如果要使用“all”选项,应先使用“read->fuse bits”读取芯片中熔丝位实际状态后,再使用“all” 选项。

(4)新的AVR芯片在使用前,应首先查看它熔丝位的配置情况,再根据实际需要,进行熔丝位的配置,并将各个熔丝位的状态记录备案。

(5)AVR芯片加密以后仅仅是不能读取芯片内部Flash和E2PROM中的数据,熔丝位的状态仍然可以读取但不能修改配置。芯片擦除命令是将Flash和E2PROM中的数据清除,并同时将两位锁定位状态配置成“11”,处于无锁定状态。但芯片擦除命令并不改变其它熔丝位的状态。

(6)正确的操作程序是:在芯片无锁定状态下,下载运行代码和数据,配置相关的熔丝位,最后配置芯片的锁定位。芯片被锁定后,如果发现熔丝位配置不对,必须使用芯片擦除命令,清除芯片中的数据,并解除锁定。然后重新下载运行代码和数据,修改配置相关的熔丝位,最后再次配置芯片的锁定位。

(7)使用ISP串行方式下载编程时,应配置SPIEN熔丝位为“0”。芯片出厂时SPIEN位的状态默认为“0”,表示允许ISP串行方式下载数据。只有该位处于编程状态“0”,才可以通过AVR的SPI口进行ISP下载,如果该位被配置为未编程“1”后,ISP串行方式下载数据立即被禁止,此时只能通过并行方式或JTAG编程方式才能将SPIEN的状态重新设置为“0”,开放ISP。通常情况下,应保持SPIEN的状态为“0”,允许ISP编程不会影响其引脚的I/O功能,只要在硬件电路设计时,注意ISP接口与其并接的器件进行必要的隔离,如使用串接电阻或断路跳线等。

(8)当你的系统中,不使用JTAG接口下载编程或实时在线仿真调试,且JTAG接口的引脚需要作为I/O口使用时,必须设置熔丝位JTAGEN的状态为“1”。芯片出厂时JTAGEN的状态默认为“0”,表示允许JTAG接口,JTAG的外部引脚不能作为I/O口使用。当JTAGEN的状态设置为“1”后,JTAG接口立即被禁止,此时只能通过并行方式或ISP编程方式才能将JTAG重新设置为“0”,开放JTAG。

(9)一般情况下不要设置熔丝位把RESET引脚定义成I/O使用(如设置ATmega8熔丝位RSTDISBL的状态为“0”),这样会造成ISP的下载编程无法进行,因为在进入ISP方式编程时前,需要将RESET引脚拉低,使芯片先进入复位状态。

(10)使用内部有RC振荡器的AVR芯片时,要特别注意熔丝位CKSEL的配置。一般情况下,芯片出厂时CKSEL位的状态默认为使用内部1MHz的RC振荡器作为系统的时钟源。如果你使用了外部振荡器作为系统的时钟源时,不要忘记首先正确配置CKSEL熔丝位,否则你整个系统的定时都会出现问题。而当在你的设计中没有使用外部振荡器(或某钟特定的振荡源)作为系统的时钟源时,千万不要误操作或错误的把CKSEL熔丝位配置成使用外部振荡器(或其它不同类型的振荡源)。一旦这种情况产生,使用ISP编程方式则无法对芯片操作了(因为ISP方式需要芯片的系统时钟工作并产生定时控制信号),芯片看上去“坏了”。此时只有使用取下芯片使用并行编程方式,或使用JTAG方式(如果JTAG为允许时且目标板上留有JTAG接口)来解救了。另一种解救的方式是:尝试在芯片的晶体引脚上临时人为的叠加上不同类型的振荡时钟信号,一旦ISP可以对芯片操作,立即将CKSEL配置成使用内部1MHz的RC振荡器作为系统的时钟源,然后再根据实际情况重新正确配置CKSEL。

(11)使用支持IAP的AVR芯片时,如果你不使用BOOTLOADER功能,注意不要把熔丝位BOOTRST设置为“0”状态,它会使芯片在上电时不是从Flash的0x0000处开始执行程序。芯片出厂时BOOTRST位的状态默认为“1”。关于BOOTRST的配置以及BOOTLOADER程序的设计与IAP的应用请参考本章相关内容。

ATmega128中重要熔丝位的配置

(1)熔丝位M103C。M103C的配置将设定ATmega128是以ATmega103兼容方式工作运行还是以ATmega128本身的方式工作运行。ATmega128在出厂时M103C默认状态为“0”,即默认以ATmega103兼容方式工作。当用户系统设计使芯片以ATmega128方式工作时,应首先将M103C的状态配置为“1”。

(2)CLKSEL0..3。CLKSEL0、CLKSEL1、CLKSEL2、CLKSEL3用于选择系统的时钟源。有五种不同类型的时钟源可供选择(每种类型还有细的划分)。芯片出厂时的默认情况为CLKSEL3..0和SUT1..0分别是“0001”和“10”。即使用内部1MHz RC振荡器,使用最长的启动延时。这保证了无论外部振荡电路是否工作,都可以进行最初的ISP下载。对于CLKSEL3..0熔丝位的改写需要十分慎重,因为一旦改写错误,会造成芯片无法启动。

(3)JTAGEN。如果不使用JTAG接口,应将JTAGEN的状态设置为“1”,即禁止JTAG,JTAG引脚用于I/O口。

(4)SPIEN。SPI方式下载数据和程序允许,默认状态为允许“0”。一般保留其状态。

(5)WDTON。看门狗的定时器始终开启。WDTON默认为“1”,即禁止看门狗的定时器始终开启。如果该位设置为“0”后,看门狗的定时器就会始终打开,不能被内部程序控制了,这是为了防止当程序跑飞时,未知代码通过写寄存器将看门狗定时器关断而设计的(尽管关断看门狗定时器需要特殊的方式,但它保证了更高的可靠行)。

(6)EESAVE。执行擦除命令时是否保留E2PROM中的内容,默认状态为“1”,表示E2PROM中的内容同Flash中的内容一同擦除。如果该位设置为“0”,对程序进行下载前的擦除命令只会对FLASH代码区有效,而对E2PROM区无效。这对于希望在系统更新程序时,需要保留E2PROM中数据的情况下是十分有用的。

(7)BOOTRST。决定芯片上电起动时,第一条执行指令的地址。默认状态为“1”,表示起动时从0x0000开始执行。如果BOOTRST设置为“0”,则起动时从BOOTLOADER区的起始地址处开始执行程序。BOOTLOADER区的大小由BOOTSZ1和BOOTSZ0决定,因此其首地址也随之变化。

(8)BOOTSZ1和BOOTSZ0:这两位确定了BOOTLOADER区的大小以及其起始的首地址。默认的状态为“00”,表示BOOTLOADER区为4096字,起始首地址为0xF000。

(9)推荐用户使用ISP方式配置熔丝位。配置工具选用BASCOM-AVR(网上下载试用版,它对ISP下载无限制),和STK200/STK300兼容的下载电缆(见第四章内容)。

注:不同AVR的熔丝也不同,使用前必须仔细查看芯片手册。

要重视手册学习,不仅是掌握如何使用,也是从根本上认识和掌握原理和结构。对于硬件工程师来将,数据手册是真正的“经书”,其它都是“修练经验”。不熟读“经书”,你无法修炼成“仙”的。这也是《M128》、《M8》的目的之一!

点击此处查看原文 >>

系统分类: 单片机   |    用户分类: 无分类    |    来源: 转贴

评论(0) | 阅读(84)
发表于:2008-4-3 15:37:48
标签:51  I2C  

1

发上个51模拟I2C通信的程序

发上个51模拟I2C通信的程序,多数51没有I2C口很麻烦啊,有用到的就拿下去看看,这程序通过Protues7.1的I2C模拟器可以通信,程序还有很多毛病,小弟正在学习阶段,欢迎斧正

/*****************************************************
/* 文件名    : I2C.h                                     
/* 描述      : I2C.c的头文件                         
/* 编写环境  : Keil uVision 3 V3.51
/* 作者      : XX
/* 学校      : 广东XX大学                                                          
/* Email     :
lanhaospider@163.com
/* 版本      : V1.0                            
/* 编写日期  : 2008-3-30                                          
/*             仅供学习参考                                      
/* 芯片      : MCS-51  AT89S52                                     
/* 晶振      : 11.0592MHz                                          
/* 功能描述  : 模拟I2C总线的接口程序库,主机的程序  
/* 应用      : 发送n个字节: 起始位->发送控制字节(类型标识符4位->
               片选3位->读写位最后1位)->应答位->数据->应答...........应答->终止位 
      高位先到达,低位后到达         
/****************************************************/

#include "reg51.h"    /*根据不同主控芯片型号改写该套入*/
#include "intrins.h"

sbit SCL = P1^6;        /*定义SCL线所在口,根据实践需要改写该定义*/
sbit SDA = P1^7;  /*定义SDA线所在口,根据实践需要改写该定义*/

unsigned char idata error; /*错误提示,全局变量*/
 
extern void Start_I2C(void);
extern void Stop_I2C(void);
extern void Ack_I2C(void);
extern void Send_Ack(void);
extern void Send_Not_Ack(void);
extern void Send_I2C(unsigned char send_byte);
extern unsigned char Receive_I2C(void);

 

 

 

/*****************************************************/
/* 文件名    : I2C.c                                     
/* 描述      : I2C通信程序                         
/* 编写环境  : Keil uVision 3 V3.51                      
/* 作者      : XX  
/* 学校      : 广东XX大学                                                
/* Email     :
lanhaospider@163.com                                
/* 版本      : V1.0
/* 编写日期  : 2008-3-30                                               
/*             仅供学习参考                                         
/* 芯片      : MCS-51  AT89S52                                          
/* 晶振      : 11.0592MHz                                           
/* 功能描述  : 模拟I2C总线的接口程序库,为主机的程序             
/*****************************************************/

#include "I2C.h"


/**************************************************
调用方式 : void Start_I2C(void)
函数说明: 启动I2C总线
**************************************************/

   void Start_I2C(void)
      {
      EA = 0;    /*关总中断*/
   SDA = 1;    /*发送启动总线的数据信号*/
   SCL = 1;    /*发送启动总线的时钟信号*/
   _nop_();       /*保持数据线高,延时*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   SDA = 0;    /*发送起始信号*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   SCL = 0;    /*时钟线高低跳变一次,I2C通信开始*/
   }  
  

/**************************************************
调用方式 : void Stop_I2C(void)
函数说明: 关闭I2C总线
**************************************************/ 
 
   void Stop_I2C(void)
      {
      SCL = 0;     /*发送关闭总线的时钟信号*/
   SDA = 0;     /*发送关闭总线的数据信号*/
   _nop_();
   _nop_();
   _nop_();      /*保持数据线低,延时*/
   _nop_();
   _nop_();
   SCL = 1;      /*时钟线一次低高跳变,I2C通信停止*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   SDA = 1;      /*发送I2C总线停止数据信号*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   EA = 1;    /*开总中断*/
   }


              
/**************************************************
调用方式 : void Ask_I2C(void)
函数说明: 主控程序等待从器件接收方式应答
**************************************************/ 

  
   void Ack_I2C(void)
      {
    unsigned char errtimes = 0xFF;
    SDA = 1;
    SCL = 1;
    error = 0x10;
    while(SDA)
       {
       errtimes--;
    if(!errtimes)
       {
          Stop_I2C();
       error = 0x11;
       return;
       }
    }
     SCL = 0;
   }


/**************************************************
调用方式 :  void Send_Ask(void)
函数说明:  主控程序为接收方,从器件为发送方时,从
            器件等待主器件应答
**************************************************/ 

   void Send_Ack(void)
   {
      SDA = 0;    /*保持数据线低,时钟线发生一次高低跳变 发送一个应答信号*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   SCL = 1;  /*时钟线保持低电平*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   SCL = 0;
   }


/**************************************************
调用方式 :  void Send_Not_Ask(void)
函数说明:  主控程序为接收方,从器件为发送方时,非应答信号          
**************************************************/ 


   void Send_Not_Ack(void)
   {
      SDA = 1;    /*保持数据线高,时钟线发生一次高低跳变 没有应答*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   SCL = 1;  /*时钟线保持高电平*/
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   SCL = 0;
   }

  
/**************************************************
调用方式 :   void Send_I2C(unsigned char send_byte)
函数说明:   总线发送一个字节         
**************************************************/  
 
  
   void Send_I2C(unsigned char send_byte)  
   {
      unsigned char send_bit;
   for(send_bit = 8;send_bit <= 0;send_bit--)
      {
      SCL = 0;
      _nop_();
      SDA = (send_byte & 0x80);
      send_byte<<=1;
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      SCL = 1;
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      _nop_();
    }
   SCL = 0;
   }
  
  
/**************************************************
调用方式 :   unsigned char Receive_I2C(void)
函数说明:   从I2C总线上接收一个字节        
**************************************************/ 

   unsigned char Receive_I2C(void)
      {
      unsigned char receive_bit , receive_byte = 0;
   for(receive_bit = 8;receive_bit <= 0;receive_bit--)
      {
      SCL = 0;
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      SCL = 1;
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      _nop_();
      receive_byte <<=1;
      receive_byte |= SDA;
   }
   SCL = 0;
   return receive_byte;
   }

点击此处查看原文 >>

系统分类: 单片机   |    用户分类: 无分类    |    来源: 原创

评论(0) | 阅读(410)
发表于:2008-3-27 20:12:11
标签:C语音  指针  

1

指针的概念

第一章。指针的概念

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针
的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身
所占据的内存区。让我们分别说明。 

先声明几个指针放着做例子: 

例一: 

(1)int *ptr; 

(2)char *ptr; 

(3)int **ptr; 

(4)int (*ptr)[3]; 

(5)int *(*ptr)[4]; 

如果看不懂后几个例子的话,请参阅我前段时间贴出的文章<<如何理解c和c 

++的复杂类型声明>>。 

 

1。 指针的类型。 

从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针
本身所具有的类型。让我们看看例一中各个指针的类型: 

(1)int *ptr; //指针的类型是int * 

(2)char *ptr; //指针的类型是char * 

(3)int **ptr; //指针的类型是 int ** 

(4)int (*ptr)[3]; //指针的类型是 int(*)[3] 

(5)int *(*ptr)[4]; //指针的类型是 int *(*)[4] 

怎么样?找出指针的类型的方法是不是很简单? 

 

2。指针所指向的类型。 

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当
做什么来看待。

从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指
向的类型。例如: 

(1)int *ptr; //指针所指向的类型是int 

(2)char *ptr; //指针所指向的的类型是char 

(3)int **ptr; //指针所指向的的类型是 int * 

(4)int (*ptr)[3]; //指针所指向的的类型是 int()[3] 

(5)int *(*ptr)[4]; //指针所指向的的类型是 int *()[4] 

在指针的算术运算中,指针所指向的类型有很大的作用。 

指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把
与指针搅和在一起的“类型”这个概念分成“指针的类型”和“指针所指向的类型”两个概念,是精通指
针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起
书来前后矛盾,越看越糊涂。

3。 指针的值,或者叫指针所指向的内存区或地址。 

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序
里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。 

指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一
片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我
们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。 

指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有
了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。 

以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪
里? 

4。 指针本身所占据的内存区。 

指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身
占据了4个字节的长度。

指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。

 

第二章。指针的算术运算

 

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例
如: 

例二: 

1。 char a[20]; 

2。 int *ptr=a; 

... 

... 

3。 ptr++; 

在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句
中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被
加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个
字节。

由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了
数组a中从第4号单元开始的四个字节。

我们可以用一个指针和一个循环来遍历一个数组,看例子: 

例三: 

int array[20]; 

int *ptr=array; 

... 

//此处略去为整型数组赋值的代码。 

... 

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

{ 

(*ptr)++; 

ptr++; 

} 

这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的
下一个单元。再看例子: 

例四: 

1。 char a[20]; 

2。 int *ptr=a; 

... 

... 

3。 ptr+=5; 

在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序
中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址
来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字
节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是
可以的。这也体现出了指针的灵活性。 

如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr
指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。 

总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类
型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘
sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地
址方向移动了n乘sizeof(ptrold所指向的类型)个字节。一个指针ptrold减去一个整数n后,结果是一个新
的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相
同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向
的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

 

第三章。运算符&和*

 

这里&是取地址运算符,*是...书上叫做“间接运算符”。&a的运算结果是一个指针,指针的类型是a的类
型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。*p的运算结果就五花八
门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址
是p所指向的地址。

例五: 

int a=12; 

int b; 

int *p; 

int **ptr; 

p=&a;//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。 

*p=24;//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。

ptr=&p;//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int**。该指针所指向的类型是p
的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。 

*ptr=&b;//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用
&b来给*ptr赋值就是毫无问题的了。

**ptr=34;//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是
一个int类型的变量。

 

第四章。指针表达式。

 

一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。下面是一些指针表达式的例
子: 

例六: 

int a,b; 

int array[10]; 

int *pa; 

pa=&a;//&a是一个指针表达式。 

int **ptr=&pa;//&pa也是一个指针表达式。 

*ptr=&b;//*ptr和&b都是指针表达式。 

pa=array; 

pa++;//这也是指针表达式。 

例七: 

char *arr[20]; 

char **parr=arr;//如果把arr看作指针的话,arr也是指针表达式 

char *str; 

str=*parr;//*parr是指针表达式 

str=*(parr+1);//*(parr+1)是指针表达式 

str=*(parr+2);//*(parr+2)是指针表达式 

由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所
指向的类型,指针指向的内存区,指针自身占据的内存。

好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一
个左值,否则就不是一个左值。

在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占
据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位
置。

 

第五章。数组和指针的关系

 

如果对声明数组的语句不太明白的话,请参阅我前段时间贴出的文章<<如何理解c和c++的复杂类型声
明>>。 数组的数组名其实可以看作一个指针。看下例: 

例八: 

int array[10]={0,1,2,3,4,5,6,7,8,9},value; 

... 

... 

value=array[0];//也可写成:value=*array; 

value=array[3];//也可写成:value=*(array+3); 

value=array[4];//也可写成:value=*(array+4); 

上例中,一般而言数组名array代表数组本身,类型是int [10],但如果把array看做指针的话,它指向数
组的第0个单元,类型是int *,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪
了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。 

例九: 

char *str[3]={ 

"Hello,this is a sample!", 

"Hi,good morning.", 

"Hello world" 

}; 

char s[80]; 

strcpy(s,str[0]);//也可写成strcpy(s,*str); 

strcpy(s,str[1]);//也可写成strcpy(s,*(str+1)); 

strcpy(s,str[2]);//也可写成strcpy(s,*(str+2)); 

上例中,str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指
针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char *。

*str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"Hello,this is 
a sample!"的第一个字符的地址,即'H'的地址。

str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char *。 

*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,good morning."的第一
个字符'H',等等。 

下面总结一下数组的数组名的问题。声明了一个数组TYPE array[n],则数组名称array就有了两重含义:
第一,它代表整个数组,它的类型是TYPE [n];第二,它是一个指针,该指针的类型是TYPE*,该指针指
向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单
独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++
的表达式是错误的。 

在不同的表达式中数组名array可以扮演不同的角色。 

在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。 

在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof
(*array)测出的是数组单元的大小。 

表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故array+n的结果是一个指针,它的
类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大
小。 

例十: 

int array[10]; 

int (*ptr)[10]; 

ptr=&array; 

上例中ptr是一个指针,它的类型是int (*)[10],他指向的类型是int [10],我们用整个数组的首地址来
初始化它。在语句ptr=&array中,array代表数组本身。 

本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还
是指针所指向的类型的大小?答案是前者。例如:

int (*ptr)[10]; 

则在32位程序中,有: 

sizeof(int(*)[10])==4 

sizeof(int [10])==40 

sizeof(ptr)==4 

实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。

 

第六章。指针和结构类型的关系

 

可以声明一个指向结构类型对象的指针。 

例十一: 

struct MyStruct 

{ 

int a; 

int b; 

int c; 

} 

MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。

MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是

MyStruct*,它指向的类型是MyStruct。

int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同
的。

请问怎样通过指针ptr来访问ss的三个成员变量? 

答案: 

ptr->a; 

ptr->b; 

ptr->c; 

又请问怎样通过指针pstr来访问ss的三个成员变量? 

答案: 

*pstr;//访问了ss的成员a。 

*(pstr+1);//访问了ss的成员b。 

*(pstr+2)//访问了ss的成员c。 

呵呵,虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规
的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元: 

例十二: 

int array[3]={35,56,37}; 

int *pa=array; 

通过指针pa访问数组array的三个单元的方法是: 

*pa;//访问了第0号单元 

*(pa+1);//访问了第1号单元 

*(pa+2);//访问了第2号单元 

从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间
没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别
的什么对齐,需要在相邻两个成员之间加若干个“填充字节”,这就导致各个成员之间可能会有若干个字
节的空隙。

所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能
访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些
填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字
节,嘿,这倒是个不错的方法。 

通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。

 

第七章。指针和函数的关系

 

 

可以把一个指针声明成为一个指向函数的指针。 

int fun1(char*,int); 

int (*pfun1)(char*,int); 

pfun1=fun1; 

.... 

.... 

int a=(*pfun1)("abcdefg",7);//通过函数指针调用函数。 

可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。 

例十三: 

int fun(char*); 

int a; 

char str[]="abcdefghijklmn"; 

a=fun(str); 

... 

... 

int fun(char*s) 

{ 

int num=0; 

for(int i=0;i<strlen(s);i++) 

{ 

num+=*s;s++; 

} 

return num; 

} 

这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指
针。在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传递给了s,s所指向的地址就和
str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算,并不意味
着同时对str进行了自加1运算。 

 

第八章。指针类型转换

 

当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达
式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向
的类型和指针表达式所指向的类型是一样的。 

例十四: 

1。 float f=12.3; 

2。 float *fptr=&f; 

3。 int *p; 

在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗? 

p=&f; 

不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是
float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指
针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试
试。为了实现我们的目的,需要进行“强制类型转换”: 

p=(int*)&f; 

如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*和TYPE,那么语法格式是: 

(TYPE*)p; 

这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址
就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。 

一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型
的转换。

例十五: 

void fun(char*); 

int a=125,b; 

fun((char*)&a); 

... 

... 

void fun(char*s) 

{ 

char c; 

c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; 

c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; 

} 

注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的
四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int 
*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结
合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译
器进行转换的过程:编译器先构造一个临时指针 char*temp,然后执行temp=(char*)&a,最后再把temp的
值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。 

我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可
以把一个整数当作指针的值直接赋给指针呢?就象下面的语句: 

unsigned int a; 

TYPE *ptr;//TYPE是int,char或结构类型等等类型。 

... 

... 

a=20345686; 

ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制) 

ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制) 

编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法: 

unsigned int a; 

TYPE *ptr;//TYPE是int,char或结构类型等等类型。 

... 

... 

a=某个数,这个数必须代表一个合法的地址; 

ptr=(TYPE*)a;//呵呵,这就可以了。 

严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的
值当作一个地址来看待。 

上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。 

想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演示了把
一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针: 

例十六: 

int a=123,b; 

int *ptr=&a; 

char *str; 

b=(int)ptr;//把指针ptr的值当作一个整数取出来。 

str=(char*)b;//把这个整数的值当作一个地址赋给指针str。 

好了,现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一
个指针。 

 

第九章。指针的安全问题

看下面的例子: 

例十七: 

char s='a'; 

int *ptr; 

ptr=(int*)&s; 

*ptr=1298; 

指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占
一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向
的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也
许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马
虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。让我们再来看一例: 

例十八: 

1。 char a; 

2。 int *ptr=&a; 

... 

... 

3。 ptr++; 

4。 *ptr=115; 

该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整
形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要
的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使
用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。 

在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。 

在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么
在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类
型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想
一想,应该会明白的。

点击此处查看原文 >>

系统分类: 软件开发   |    用户分类: 无分类    |    来源: 转贴

评论(0) | 阅读(246)
发表于:2008-3-14 15:51:49
标签:单片机  DS1820  

3

DS18B20的温度计

点击下载这是我设计的DS18B20的温度计,用PROTUES仿真的(原谅我没焊它出来)DS18B20是个12位的慢速温度转换器件,单总线设计,本例用寄生电源供电,由于开始时没有完全按照DS18B20的读写时序写程序导致无法读取器件,单总线器件对读写时序要求较高,必须按照其读写时序操作,12位转换速度很慢才750ms9位也要93.75ms,在对温度实时性要求不高的地方可以用,如体温计(可精确到0.0625,可通过运算精确到0.1,不过8位单片机的运算能力不敢恭维)运用在电饭煲上9位转换的时间还是可以达到要求的,且这单总线器件特别适用于环境恶劣的地方,成本较高(单买要7-10块,量买应该可以在2-3块吧),本程序是用12位转换实现精确到个位的温度计程序
写本程序时充分体会到汇编的精细要求(一条语句有点点问题都可能影响整个程序,这是细心的活,要耐心),还有DS18B20单总线的时序要求的严格性
下面将我的电路图还有汇编程序给大家
点击看大图
;----------------------------------------------
;temperature.asm
;
作者:老猫
;
作用:利用温度传感器测温,并显示出来,精度为个位
;
日期:2008-3-14
;版本:1.0
;MCU
AT89S51
;
晶振频率:12MHz
;DS18B20
P3.4用寄生电源供电
;P0
接阴极数码管显示位
;P2^1-4
分别接数码管个十百千位
;-----------------------------------------------

DQ  bit  p3.4 
;定义DQ为单片机和DS18B20通讯管脚
FLAG BIT  01H       
DS18B20存在标记
TMPL data 029h         
12位温度高位
TMPH data 028h            
12位温度低位
TMP  DATA 027H           
;转换为8位后温度字节
org  0000h
ljmp main
main:MOV SP,#0CFH          
;初始化堆栈
CLR  FLAG
LCALL RESET 
;初始化DS18B20
JNB  FLAG,CZ
;判断DS18B20存在与否
MOV  TMP,#11
;告知DS18B20存在
LCALL DISPLAY
RE: LCALL RESET
;重复调用DS18B20和显示
MOV  A,#0CCH
;跳过搜索ROM
LCALL WRITE
NOP
MOV  A,#044H
;发送命令转换温度,12位最大转换时间750MS
LCALL   WRITE
MOV  R5,#30
REDISPLAY: LCALL DISPLAY
DJNZ R5,REDISPLAY
LCALL RESET
;重新复位DS18B20
MOV  A,#0CCH
;跳过ROM
LCALL  WRITE
LCALL GET_TEMPER
;读取温度
LCALL TRANS_TEMP
;由12位温度转换为8位温度
LCALL DISPLAY
;显示
SJMP RE
CZ: MOV  TMP,#99
SJMP REDISPLAY
;================
温度转换===============
TRANS_TEMP:
;温度低位高4位和温度高位的低4位为我们需要的多种方法实现,此方法周期长,占用字节多,且要用到位寻址地址,资源浪费严重
   MOV C,28H.3
   MOV 27H.7,C
   MOV C,28H.2
   MOV 27H.6,C
   MOV C,28H.1
   MOV 27H.5,C
   MOV C,28H.0
   MOV 27H.4,C
   MOV C,29H.7
   MOV 27H.3,C
   MOV C,29H.6
   MOV 27H.2,C
   MOV C,29H.5
   MOV 27H.1,C
   MOV C,29H.4
   MOV 27H.0,C
   mov a,27h
   RET
;==============
显示=====================
DISPLAY:
PUSH ACC
;现场保存
PUSH PSW
PUSH B
PUSH TMP
MOV P2,#0
MOV DPTR,#NUM
MOV A,TMP  ;
显示前两位tmp除以10得到高低位(对零下和高于100的温度无效)
MOV B,#10
DIV AB  ;
低位/10,余数是个位,商是十位
MOVC A,@A+DPTR
MOV P0,A
SETB P2.1
LCALL DELAY
MOV A,B
MOVC A,@A+DPTR
CLR  P2.1
MOV P0,A
SETB P2.0
LCALL DELAY
RETURN: POP TMP    
;现场恢复
POP B
POP PSW
POP ACC
RET
;==============
读取温度数据================
GET_TEMPER: JNB  FLAG,R_T_NO
;判断器件存在与否
  MOV  A,#0BEH
;发送0BE读取RAM上内容
  LCALL WRITE
  LCALL READ
  MOV  TMPL,A
;先低位后高位
  LCALL READ
  MOV  TMPH,A
  RET
R_T_NO:MOV  TMPH,#0H
       MOV  TMPL,#0H
    RET
;============
初始化18B20子程序===============
;
实现时序由单片机负跳转保持低电平480us然后放开总线,等待60us后由DS18B20发出存在脉冲总共480us
RESET:
SETB DQ
NOP
NOP
CLR DQ
MOV R7,#3
START_DEL1: MOV R6,#107
DJNZ R6,$
DJNZ R7,START_DEL1
SETB DQ
NOP
NOP
MOV R7,#100
START_RELAY: JNB DQ,HAVE1820
DJNZ R7,START_RELAY
CLR FLAG
SJMP START_OUT
HAVE1820:
SETB FLAG
MOV R6,117
DJNZ R6,$
START_OUT:
SETB DQ
RET  
;================
读字节======================
READ:
;读时序单片机负跳转后等待1-2us15us内读取高低,然后等待45us后又开始读取下一位,每字节8
MOV R7,#8
READ_BIT: SETB DQ
CLR C
NOP
CLR DQ
NOP
NOP
SETB DQ
MOV R6,#6
DJNZ R6,$
MOV C,DQ
RRC A
MOV R6,#25
DJNZ R6,$
DJNZ R7,READ_BIT
RET
;=================
写字节======================
WRITE:
;写时序,负跳转后等待15us内写入01,保持45us,再开始下一位写时序,每字节8
MOV R7,#8
CLR C
WRITE_BIT:
SETB DQ
NOP
NOP
CLR DQ
MOV R6,#6
DJNZ R6,$
RRC A
MOV DQ,C
MOV R5,#23    ;
25改为6
DJNZ R5,$
SETB DQ
NOP
DJNZ R7,WRITE_BIT
SETB DQ
RET

;---------------
延时6ms--------------
DELAY:PUSH PSW
SETB RS0
SETB RS1
MOV R6,#0EH      ;
延时6MS
DEL1: MOV R7,#0C0H
DJNZ R7,$
DJNZ R6,DEL1
POP  PSW
RET
  ;============
数码管显示==============
NUM:DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H
END

点击此处查看原文 >>

系统分类: 单片机   |    用户分类: 无分类    |    来源: 原创

评论(3) | 阅读(573)
发表于:2008-3-14 15:48:01
标签:无标签

0

困扰我几天的,差一点点,PROTUES仿真居然用不了

 

DS1820及其高精度 温度测量的实现

摘 要: 结合数字温度传感器DS1820在水轮发电机组轴瓦温度测量中的应用经验,提出了用DS1820实现轴瓦温度高精度、高可靠性测量的可行性方案。
    关键词: 数字温度传感器 DS1820 高精度 温度测量
    在传统的模拟信号远距离温度测量系统中,需要很好的解决引线误差补偿问题、多点测量切换误差问题和放大电路零点漂移误差问题等技术问题,才能够达到较高的测量精度。我们在为某水电站开发水轮发电机组轴瓦温度实时监测系统时,为了克服上面提到的三个问题,采用了新型数字温度传感器DS1820,在对其测温原理进行详细分析的基础上,提出了提高DS1820测量精度的方法,使DS1820的测量精度由0.5℃提高到0.1℃以上,取得了良好的测温效果。
    1 DS1820简介
    DS1820是美国DALLAS半导体公司生产的可组网数字式温度传感器,在其内部使用了在板(ON-B0ARD)专利技术。全部传感元件及转换电路集成在形如一只三极管的集成电路内。与其它温度传感器相比,DS1820具有以下特性:
    (1)独特的单线接口方式,DS1820在与微处理器连接时仅需要一条口线即可实现微处理器与DS1820的双向通讯。
2)DS1820支持多点组网功能,多个DS1820可以并联在唯一的三线上,实现多点测温。
    (3)DS1820在使用中不需要任何外围元件。
    (4)温范围-55℃~+125℃,固有测温分辨率0.5℃。
    (5)测量结果以9位数字量方式串行传送。
    DS1820内部结构框图如图1所示。


DS1820测温原理如图2所示。图中低温度系数晶振的振荡频率受温度影响很小,用于产生固定频率的脉冲信号送给计数器1。高温度系数晶振随温度变化其振荡率明显改变,所产生的信号作为计数器2的脉冲输入。计数器1和温度寄存器被预置在-55℃所对应的一个基数值。计数器1对低温度系数晶振产生的脉冲信号进行减法计数,当计数器1的预置值减到0时,温度寄存器的值将加1 ,计数器1的预置将重新被装入,计数器1重新开始对低温度系数晶振产生的脉冲信号进行计数,如此循环直到计数器2计数到0时,停止温度寄存器值的累加,此时温度寄存器中的数值即为所测温度。图2中的斜率累加器用于补偿和修正测温过程中的非线性,其输出用于修正计数器1的预置值。
在正常测温情况下,DS1820的测温分辩率为0.5℃以9位数据格式表示,其中最低有效位(LSB)由比较器进行0.25℃比较,当计数器1中的余值转化成温度后低于0.25℃时,清除温度寄存器的最低位(LSB),当计数器1中的余值转化成温度后高于0.25℃,置位温度寄存器的最低位(LSB),如-25.5℃对应的9位数据格式如下:
    2 提高DS1820测温精度的途径
    2.1 DS1820高精度测温的理论依据
    DS1820正常使用时的测温分辨率为0.5℃,这对于水轮发电机组轴瓦温度监测来讲略显不足,在对DS1820测温原理详细分析的基础上,我们采取直接读取DS1820内部暂存寄存器的方法,将DS1820的测温分辨率提高到0.1℃~0.01℃.
    DS1820内部暂存寄存器的分布如表1所示,其中第7字节存放的是当温度寄存器停止增值时计数器1的计数剩余值,第8字节存放的是每度所对应的计数值,这样,我们就可以通过下面的方法获得高分辨率的温度测量结果。首先用DS1820提供的读暂存寄存器指令(BEH)读出以0.5℃为分辨率的温度测量结果,然后切去测量结果中的最低有效位(LSB),得到所测实际温度整数部分T整数,然后再用BEH指令读取计数器1的计数剩余值M剩余和每度计数值M每度,考虑到DS1820测量温度的整数部分以0.25℃、0.75℃为进位界限的关系,实际温度T实际可用下式计算得到:


T实际=(T整数-0.25℃)+(M每度-M剩余)/M每度
    2.2 测量数据比较
    表2为采用直接读取测温结果方法和采用计算方法得到的测温数据比较,通过比较可以看出,计算方法在DS1820测温中不仅是可行的,也可以大大的提高DS1820的测温分辨率。


3 DS1820使用中注意事项
    DS1820虽然具有测温系统简单、测温精度高、连接方便、占用口线少等优点,但在实际应用中也应注意以下几方面的问题:
    (1)较小的硬件开销需要相对复杂的软件进行补偿,由于DS1820与微处理器间采用串行数据传送,因此,在对DS1820进行读写编程时,必须严格的保证读写时序,否则将无法读取测温结果。在使用PL/M、C等高级语言进行系统程序设计时,对DS1820操作部分最好采用汇编语言实现。
    (2)在DS1820的有关资料中均未提及单总线上所挂DS1820数量问题,容易使人误认为可以挂任意多个DS1820,在实际应用中并非如此。当单总线上所挂DS1820超过8个时,就需要解决微处理器的总线驱动问题,这一点在进行多点测温系统设计时要加以注意。
    (3)连接DS1820的总线电缆是有长度限制的。试验中,当采用普通信号电缆传输长度超过50m时,读取的测温数据将发生错误。当将总线电缆改为双绞线带屏蔽电缆时,正常通讯距离可达150m,当采用每米绞合次数更多的双绞线带屏蔽电缆时,正常通讯距离进一步加长。这种情况主要是由总线分布电容使信号波形产生畸变造成的。因此,在用DS1820进行长距离测温系统设计时要充分考虑总线分布电容和阻抗匹配问题。
    (4)在DS1820测温程序设计中,向DS1820发出温度转换命令后,程序总要等待DS1820的返回信号,一旦某个DS1820接触不好或断线,当程序读该DS1820时,将没有返回信号,程序进入死循环。这一点在进行DS1820硬件连接和软件设计时也要给予一定的重视。
    参考文献
    1 何立民.单片机应用技术选编(1~6).北京:北京航空航天大学出版社,1997
    2 D.斯托尔.工业抗干扰的理论与实践.北京:国防工业出版社,1985
    3 Dallas  Semiconductor data books.Dallas Semiconductor   Corporation 1995

附录:利用AT89C2051单片机与DS18B20和两个数码管显示温度(51汇编)
;这是关于DS18B20的读写程序,数据脚P3.4,晶振12MHZ

;温度传感器18B20汇编程序,采用器件默认的12位转化,最大转化时间750微秒
;可以将检测到的温度直接显示到连接到AT89C2051的两个数码管上
;显示温度00到99度,很准确哦~~无需校正!
  ORG  0000H
;单片机内存分配申明!

TEMPER_L  EQU  29H  ;用于保存读出温度的低8位
TEMPER_H  EQU  28H  ;用于保存读出温度的高8位
FLAG1   EQU  38H  ;是否检测到DS18B20标志位
A_BIT   EQU  20H   ;数码管个位数存放内存位置
B_BIT   EQU  21H   ;数码管十位数存放内存位置
MAIN:   LCALL  GET_TEMPER ;调用读温度子程序
;显示范围00到99度,显示精度为1度

;因为12位转化时每一位的精度为0.0625度,我们不要求显示小数所以可以抛弃29H的低4位
;将28H中的低4位移入29H中的高4位,这样获得一个新字节,这个字节就是实际测量获得的温度
;这个转化温度的方法非常简洁,无需乘于0.0625系数
  MOV  A,29H

  MOV  C,40  ;将28H中的最低位移入C
  RRC  A
  MOV  C,41H
  RRC  A
  MOV  C,42H
  RRC  A
  MOV  C,43H
  RRC  A
  MOV  29H,A
  LCALL  DISPLAY  ;调用数码管显示子程序
  AJMP  MAIN
;这是DS18B20复位初始化子程序

INIT_1820: SETB  P3.4
  NOP
  CLR  P3.4
;主机发出延时537微秒的复位低脉冲
  MOV  R1,#3
TSR1:  MOV  R0,#107
  DJNZ  R0,$
  DJNZ  R1,TSR1
  SETB  P3.4  ;然后拉高数据线
  NOP
  NOP
  NOP
  MOV  R0,#25H
TSR2:  JNB  P3.4,TSR3 ;等待DS18B20回应
  DJNZ  R0,TSR2  ;延时
  LJMP  TSR4  
TSR3:  SETB  FLAG1   ;置标志位,表示DS1820存在
  LJMP  TSR5
TSR4:  CLR  FLAG1   ;清标志位,表示DS1820不存在
  LJMP  TSR7
TSR5:  MOV  R0,#117

TSR6:  DJNZ  R0,TSR6  ;时序要求延时一段时间
TSR7:  SETB  P3.4
  RET
;读出转换后的温度值
GET_TEMPER: SETB  P3.4

  LCALL  INIT_1820 ;先复位DS18B20
  JB  FLAG1,TSS2
  RET    ;判断DS1820是否存在?若DS18B20不存在则返回
TSS2:  MOV  A,#0CCH  ;跳过ROM匹配
  LCALL  WRITE_1820
  MOV  A,#44H   ;发出温度转换命令
  LCALL  WRITE_1820
;这里通过调用显示子程序实现延时一段时间,等待AD转换结束,12位的话750微秒
  LCALL  DISPLAY
  LCALL  INIT_1820 ;准备读温度前先复位
  MOV  A,#0CCH  ;跳过ROM匹配

  LCALL  WRITE_1820
  MOV  A,#0BEH  ;发出读温度命令

  LCALL  WRITE_1820
  LCALL  READ_18200 ;将读出的温度数据保存到35H/36H
  RET
;写DS18B20的子程序(有具体的时序要求)

WRITE_1820: MOV  R2,#8  ;一共8位数据
  CLR  C
WR1:  CLR  P3.4
  MOV  R3,#6
  DJNZ  R3,$
  RRC  A
  MOV  P3.4,C
  MOV  R3,#23
  DJNZ  R3,$
  SETB  P3.4
  NOP
  DJNZ  R2,WR1
  SETB  P3.4
  RET
;读DS18B20的程序,从DS18B20中读出两个字节的温度数据

READ_18200: MOV  R4,#2   ;将温度高位和低位从DS18B20中读出
  MOV  R1,#29H  ;低位存入29H(TEMPER_L),高位存入28H(TEMPER_H)
RE00:  MOV  R2,#8  ;数据一共有8位
RE01:  CLR  C
  SETB  P3.4
  NOP
  NOP
  CLR  P3.4
  NOP
  NOP
  NOP
  SETB  P3.4
  MOV  R3,#9

RE10:   DJNZ  R3,RE10
  MOV  C,P3.4
  MOV  R3,#23

RE20:   DJNZ  R3,RE20
  RRC  A

  DJNZ  R2,RE01
  MOV  @R1,A
  DEC  R1
  DJNZ  R4,RE00
  RET
;显示子程序
DISPLAY:  MOV  A,29H  ;将29H中的十六进制数转换成10进制

  MOV  B,#10   ;10进制/10=10进制
  DIV  AB
  MOV  B_BIT,A  ;十位在a
  MOV  A_BIT,B  ;个位在b
  MOV  DPTR,#NUMTAB  ;指定查表启始地址
  MOV  R0,#4