Easy 51Pro编程器的原理与扩充之Easy ISP

 

如果你看完了《Easy 51Pro编程器的原理与扩充》,那么你看这篇文章将更加容易。你会发现ProWorkclass CIsPro的原理完全一样,其实ProWork就是模仿了CIsPro。我管CIsPro叫“ISP编程方法类”。

 

看本文,你可能需要一点C++的基础知识。本文主要是帮助用户掌握对EasyISP进行扩充的原理和方法,所以讨论重点将放在CParlProCIsPro,需要了解其他细节可以去看源程序。另外附VC源代码,在VC6.0上就可以编译出一个EPro.exe的可执行文件。不过本程序使用了Code JockXtreme Toolkit界面解决方案,需要先下载安装(至少要装15分钟)。如果编译时提示找不到dll,请把winio.dllwinio.syshook.dllXT2000Lib.dll拷贝到程序所在的目录

      

Easy 51Pro的应用程序框架:

CEProDlg对话框界面接收用户操作,把用户操作转换成对CPro对象中的函数调用

CPro:根据用户的选择,建立CParlPro对象(如果使用并口下载线)CSerialPro对象(如果使用串行编程器);管理CParlPro对象和CSerialPro对象,为它们提供与用户界面的通讯;建立两快64k的缓冲1和缓冲2,提供一些管理缓冲的函数;管理器件列表,可以通过特征字查询到与之匹配的器件

CParlPro派生自CParallelPort

CParallelPort:负责安装驱动程序,提供丰富的函数方便对并口进行操作。

CParlPro:封装了ISP编程的一般流程,通过CIsPro的派生类对象控制并口对器件进行编程。

CSerialPro派生自CSerialPort

CSerialPort:提供丰富的函数方便串口通讯

CSerialPort:把CPro传递过来的用户操作信息转换成编程命令按照协议发送给编程器

 

       由于CParallelPort中设计了这样一些函数:

 

              BOOL SetPinLogic(int nPin,BOOL bLogic);        //设置指定引脚,bLogic=1高电平,bLogic=0低电平

              BOOL GetPinLogic(int nPin);                             //得到指定引脚的电平

              BOOL SetPinL(int nPin);                                   //设置指定引脚为低电平

              BOOL SetPinH(int nPin);                                   //设置指定引脚为高电平

 

他们可以通过并口引脚号对并口的某个引脚进行操作,这就使Easy ISP天生具备支持任何下载线的能力。看看是怎么支持的:

 

              int   m_nPinMosi;                                           //控制MOSI所用的并口引脚

              int    m_nPinMiso;                                           //控制MISO所用的并口引脚

              int    m_nPinRst;                                             //控制RST所用的并口引脚

              int    m_nPinSck;                                            //控制SCK所用的并口引脚

              int    m_nPinLe;                                              //控制器件锁存所用的并口引脚

              int    m_nPinOe;                                              //控制器件OE所用的并口引脚

              int    m_nPinR1;                                              //保留引脚1

              int    m_nPinR2;                                              //保留引脚2

              BOOL m_bLe;                                                  //锁存有效时的电平

              BOOL m_b2Le;                                                //锁存无效时的电平

              BOOL m_bOe;                                                 //OE有效时的电平

              BOOL m_b2Oe;                                               //OE无效时的电平

 

上面这一些变量保存了下载线用到的所有并口引脚号。这么多引脚资源,应该够用了吧。要知道MOSIMISOSCK这些引脚的用途可以看《Easy 51Pro编程器的原理与扩充》。在初始化的时候会把这些变量赋予设定的值。

      

if(nProType==1)//Easy ISP下载线

       {

              CString strEasyIspIni;

              strEasyIspIni=m_strAppPath+"EasyIsp.ini";//EasyIsp.ini文件获得引脚配置信息

              //如果不存在"EasyIsp.ini"文件,则使用默认的下载线配置(老版本的)

              m_nPinMosi=GetPrivateProfileInt("引脚控制","MOSI",14,strEasyIspIni);

              m_nPinMiso=GetPrivateProfileInt("引脚控制","MISO",15,strEasyIspIni);

              m_nPinSck=GetPrivateProfileInt("引脚控制","SCK",1,strEasyIspIni);

              m_nPinRst=GetPrivateProfileInt("引脚控制","RST",16,strEasyIspIni);

              m_nPinLe=GetPrivateProfileInt("引脚控制","LE",17,strEasyIspIni);

              m_nPinOe=GetPrivateProfileInt("引脚控制","OE",2,strEasyIspIni);

              m_nPinR1=GetPrivateProfileInt("引脚控制","R1",3,strEasyIspIni);

              m_nPinR2=GetPrivateProfileInt("引脚控制","R2",4,strEasyIspIni);

              m_bLe=GetPrivateProfileInt("锁存控制(LE)","Enable",1,strEasyIspIni);

              m_b2Le=GetPrivateProfileInt("锁存控制(LE)","Disable",0,strEasyIspIni);

              m_bOe=GetPrivateProfileInt("输出控制(OE)","Enable",0,strEasyIspIni);

              m_b2Oe=GetPrivateProfileInt("输出控制(OE)","Disable",1,strEasyIspIni);

       }

如果在设置“编程器”中,选择了EasyISP,那么程序将从EasyIsp.ini中载入引脚的配置信息。

如果程序目录不存在该文件呢?那就是支持默认的下载线,这个下载线是原来设计的,这样就解决了程序向上兼容的问题。如果设置“编程器”中选择了Atmel ByteBlaster下载线或Altera ByteBlaster下载线,那么就会进行下面这些配置。

 

       else if(nProType==2)                                 //Atmel ByteBlaster下载线

       {

              m_nPinRst=PIN_SELIN;                    //PIN_SELIN这些在ParllelPort.h中做了定义,这是并口引脚的功能号

              m_nPinMosi=PIN_D0;

              m_nPinMiso=PIN_ACK;

              m_nPinSck=PIN_STROBE;

              m_nPinLe=PIN_D2;                           //不用LE,为了延时假定一个不起作用的引脚

              m_bLe=0;

              m_b2Le=0;

              m_nPinOe=0;                                     //不用OE

              m_nPinR1=m_nPinAf=PIN_AUTO;     //保留

              m_nPinR2=m_nPinIni=PIN_INIT;       //保留

}

       else if(nProType==3)                                 //Altera ByteBlaster下载线

       {

              m_nPinRst=3;

              m_nPinMosi=8;

              m_nPinMiso=11;

              m_nPinSck=2;

              m_nPinLe=14;                                   //控制74244LE

              m_bLe=0;

              m_b2Le=0;                                        //LE常置低电平

              SetPinL(14);                                      //先预置74244'LE为低电平

              m_nPinOe=0;                                     //没有OE

              m_nPinR1=0;                                    

              m_nPinR2=0;                                     //无保留引脚

       }

 

有些东西要提示一下。m_nPinOe是控制器件锁存的并口引脚号,但74244没有OE怎么办呢,那就把m_nPinOe赋为0吧,并口是没有PIN 0的。还有就是对器件LE的控制。m_bLe表示锁存有效时的电平,例如74373LE高电平有效,所以m_bLe=1

m_b2Le表示锁存无效时的电平,所以74373m_b2Le=0;如果需要74373LE常开启怎么办呢?m_bLe=1m_b2Le=1不就解决问题了吗!同样还有m_bOe,m_b2Oe。看看m_nPinLe主要用在了哪里吧。

 

void CParallelPro::SetSck(BOOL bLogic)     //设置SCK引脚的电平

{

       if(bLogic)

              SetPinH(m_nPinSck);

       else

              SetPinL(m_nPinSck);

       if(m_nIspSpd==2)                              //如果性能设置为“最快”

       {

              return;

       }

       else if(m_nIspSpd==1)                       //如果性能设置为“较快”

       {

              SetPinLogic(m_nPinLe,m_bLe);    //开启锁存

              return;

       }

       else                                                   //如果性能设置为“一般”

       {

              SetPinLogic(m_nPinLe,m_bLe);    //开启锁存

              for(int n=0;n<=1000;n++)            //延时,在LE产生脉冲宽度

              {

              }

              SetPinLogic(m_nPinLe,m_b2Le);  //关闭锁存

       }

}

 

这段程序的意思是如果性能设置为“一般”,SCK上的信号改变一次,就锁存一次。如果性能设置为“较快”则把LE开启,但不关闭。你肯定要问为什么每次都要开启呢,即使LE重来没有关闭过?因为这样可以多一次对并口的访问,访问一次并口的会消耗一定的时间,这样就可以当作极短的延时。所以即使器件没有LE,或者74244LE接到了GND,也可以假定一个吗!如果设置为“最快”呢,就不用锁存了。性能设置为“一般”时,有一个锁存过程,锁存后可以增强抗干扰。

 

下面这段程序可以参照一下《Easy 51Pro的原理与扩充》中对void SendInstrc(BYTE nByte)的解释,原理都是一样,不过这里的程序要获得“位”就没有单片机里那么容易了。

 

void CParallelPro::SckBytes(int nBytes)        //通过下载线与器件通信

{

       for(int n=0;n<nBytes;n++)                  //要输出的字节数

       {    

              SetSck(0);

              SetMosi((OutBuf[n] & 0x80));     //SCK为低电平时,发送一位

              SetSck(1);

              if(GetMiso())                              //SCK为高电平时,接收一位

              {

             

                     InBuf[n]=InBuf[n] | 0x80;    

              }

              else

              {    

                     InBuf[n]=InBuf[n] & 0x7f;

              }

              SetSck(0);

              SetMosi((OutBuf[n] & 0x40));

              SetSck(1);

             

             

}

 

上面那些程序主要是关于对并口控制的,主要是方便你应用。下面介绍一下CParlPro控制编程的主思路。先介绍一下CIsPro是什么?刚说过CIsPro是“ISP编程方法类”。它是一个纯虚类,只有函数定义,没有函数实现,就像还没填数额的支票,当然兑不到钱。

 

class CIsPro 

{

public:

       BYTE FID;                                                      //该类所支持的FID

       CParallelPro* m_pParlPro;                                //方便调用到CParallelPro中的资源

       virtual void InitIsPro(CParallelPro* pParlPro);    

       virtual void PreparePro()=0;                              //编程前的工作

       virtual void ReadSign(BYTE* pBuf)=0;               //读特征字

       virtual void Erase()=0;                                      //擦除器件

       virtual BOOL Write(BYTE Data,int nAddr)=0;      //写一个单元

       virtual BYTE Read(int nAddr)=0;                       //读一个单元

       virtual BOOL LockBit(int nBit)=0;                      //写锁定位

       virtual void ProOver()=0;                                  //编程结束后的工作

       CIsPro();

       virtual ~CIsPro();

};

 

这个类定义了对器件编程的一般操作,是不是和串行编程器中的ProWork很相似?从这个类派生出对器件编程的具体方法。再看看这个CIsPro类是怎么被应用的。以AT89S51为例(因为我手头上只有这种芯片)。现在就是给支票填数额了。

 

//At89s51Isp.h

class CAt89s51Isp : public CIsPro 

{

public:

       CAt89s51Isp();

       virtual ~CAt89s51Isp();

       virtual void InitIsPro(CParallelPro* pParlPro);

       virtual void PreparePro();                                  //编程前的工作

       virtual void ReadSign(BYTE* pBuf);                   //读特征字

       virtual void Erase();                                          //擦除器件

       virtual BOOL Write(BYTE Data,int nAddr);         //写一个单元

       virtual BYTE Read(int nAddr);                           //读一个单元

       virtual BOOL LockBit(int nBit);                          //写锁定位

       virtual void ProOver();                                      //编程结束后的工作

};

 

还是挑几个出来看看究竟吧,最好对照一下DataSheet上的那个表。

 

//At89s51Isp.cpp

void CAt89s51Isp::Erase()//擦除器件

{

       m_pParlPro->OutBuf[0]=0xac;                          //根据器件手册上规定的命令协议

       m_pParlPro->OutBuf[1]=0x80;

       m_pParlPro->SckBytes(4);                                //向器件发编程命令,4个字节

       Sleep(500);                                                      //擦除器件要500ms

}

 

BOOL CAt89s51Isp::Write(BYTE Data,int nAddr)       //写一个单元

{

       int nTimeOut=0;

       m_pParlPro->OutBuf[0]=0x40;                          //根据器件手册上规定的命令协议

       m_pParlPro->OutBuf[1]=((BYTE*)&nAddr)[1];  //高地址

       m_pParlPro->OutBuf[2]=((BYTE*)&nAddr)[0];  //低地址

       m_pParlPro->OutBuf[3]=Data;

       m_pParlPro->SckBytes(4);                                //向器件发编程命令

       while(Read(nAddr)!=Data)                                //效验:循环读,直到读出与写入的数相同

       {

              nTimeOut++;

              if(nTimeOut>=1000)                                 //如果超时了,写入失败

                     return FALSE;

       }

       return TRUE;

}

 

BYTE CAt89s51Isp::Read(int nAddr)                         //读一个单元

{

       m_pParlPro->OutBuf[0]=0x20;                          //根据器件手册上规定的命令协议

       m_pParlPro->OutBuf[1]=((BYTE*)&nAddr)[1];  //高地址

       m_pParlPro->OutBuf[2]=((BYTE*)&nAddr)[0];  //低地址

       m_pParlPro->SckBytes(4);                                //向器件发编程命令

       return m_pParlPro->InBuf[3];                           //该单元的数据

}

 

void CAt89s51Isp::PreparePro()                                //编程前的准备工作

{

       m_pParlPro->SetRst(0);                                    //RST置低电平

       m_pParlPro->SetMosi(0);                                 //MOSI置低电平

       m_pParlPro->SetSck(0);                                   //SCK置低电平

       Sleep(10);

       m_pParlPro->SetRst(1);                                    //编程前RST要置高点平

       Sleep(10);

       m_pParlPro->OutBuf[0]=0xac;                          //注意这里,按照ATMEL DataSheet的规定,任何编程操作前

       m_pParlPro->OutBuf[1]=0x53;                          //必须先发送Programming Enable的命令,安排在这里最合适

       m_pParlPro->SckBytes(4);                               

}

还是贴那个出表来看一下吧!

             

 

还有一个函数千万别忘了:

 

CAt89s51Isp::CAt89s51Isp()

{

              m_pParlPro=NULL;

              FID=0x02;                                               //该类所支持的FID

}

 

最后再看看CParlPro是如何使用CIsPro的:

CParlPro有个这样的东西:

 

CArray<CIsPro*,CIsPro*> m_arIsp;                         //Isp编程方法队列

 

CParlPro的构造函数中:

CParallelPro::CParallelPro()

{

              m_arIsp.Add(new CAt89s51Isp);               //把所有的Isp编程方法对象加入到队列

}

 

当用户对选择的某器件编程时须要先得到该器件的编程方法

 

CIsPro* CParallelPro::GetIsPro(BYTE FID)              //查找支持该FID"Isp编程方法对象"

{    

       for(int n=0;n<m_arIsp.GetSize();n++)

              if(m_arIsp.GetAt(n)->FID==FID)               //从队列中找出支持该器件FID的编程方法

                     return m_pIsPro=m_arIsp.GetAt(n);    //设置当前"Isp编程方法对象"指针

                    

       return NULL;

}

 

例如用户发出擦除AT89S51的命令后:

 

void CParallelPro::Erase(BYTE FID)

{

       if(m_bThread)                                                 //如果上一次操作线程还没结束

       {

              m_pPro->Notify(PRO_INVALID);

              return;

       }

       if(GetIsPro(FID)==NULL)                                //查询是否支持该FID,并获得编程方法

       {

              m_pPro->Notify(PRO_WORK_INVALID);

              return;

       }

       m_nCurWork=2;                                              //当前操作标识

       AfxBeginThread(ProWorkThread,this);              

}

 

获得编程方法后当然是要使用该编程方法了,使用编程方法是在ProWorkThread线程中进行的,创建另外一个线程就是为了避免在读,写这些编程过程中,窗口界面停止响应。ProWorkThread是如何使用这些编程方法的,你一看源代码就知道了。

 

最后再总结一下扩充Easy ISP的步骤:

步骤123,和《Easy 51Pro的原理与扩充》中介绍的一样。

4.从CIsPro中派生出一个类,实现这个类中的所有函数。最好以CAt89s51Isp为模板,修改一下就可以了。还有记得这里:

 

CParallelPro::CParallelPro()

{

              m_arIsp.Add(new CAt89s51Isp);               //把所有的Isp编程方法对象加入到队列

              m_arIsp.Add(new 你的Isp方法类);          //<<<<<<<<<<<<<<<<<<<<<<<

}

 

5.测试你的程序,成功后把它贴出来与大家分享,可以先发个Email给我。