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

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

看本文，你可能需要一点 C++的基础知识。本文主要是帮助用户掌握对 EasyISP 进行扩充的原理和方法，所以讨论重点将放在 CParlPro 和 CIsPro，需要了解其他细节可以去看源程序。另外附 VC 源代码，在 VC6.0 上就可以编译出一个 EPro.exe 的可执行文件。不过本程序使用了 Code Jock 的 Xtreme Toolkit 界面解决方案，需要先下载安装(至少要装 15 分钟)。如果编译时提示找不到 dll，请把 winio.dll，winio.sys，hook.dll，XT2000Lib.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 无效时的电平

上面这一些变量保存了下载线用到的所有并口引脚号。这么多引脚资源，应该够用了吧。要知道 MOSI，MISO，SCK 这些引脚的用途可以看《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; //控制 74244 的 LE

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 表示锁存有效时的电平，例如 74373 的 LE 高电平有效，所以 m_bLe=1，

m_b2Le 表示锁存无效时的电平，所以 74373 的 m_b2Le=0；如果需要 74373 的 LE 常开启怎么办呢？m_bLe=1，m_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，或者 74244 的 LE 接到了 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 的步骤：

步骤 1，2，3，和《Easy 51Pro 的原理与扩充》中介绍的一样。

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

CParallelPro::CParallelPro()

{

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

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

}

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