精简ISA总线WinCE编程

 2019-4-16 15:50:35     作者:刘乾坤    

ISA总线简介


  英创精简ISA总线接口是一种8-bit宽度的双向并行扩展总线,其特点是地址数据分时复用8-bit总线。英创精简ISA总线支持异步和同步访问模式,异步访问模式下ISA总线使用8条数据线加上4条总线控制信号,即可实现对外部数据的快速读写,异步访问的最高速率在5MB/s左右。同步访问需要再使能一条总线时钟信号(共13条信号),可实现高达12MB/s的数据传输。精简ISA总线作为英创主板的特色功能之一,在ESM6802、ESM7000、ESM335x等多款型号中均有配置。英创精简ISA总线的信号定义如下:


主板引脚信号及说明
E2ISA_D0,地址数据总线,LSB
E3ISA_D1,地址数据总线
E4ISA_D2,地址数据总线
E5ISA_D3,地址数据总线
E6ISA_D4,地址数据总线
E7ISA_D5,地址数据总线
E8ISA_D6,地址数据总线
E9ISA_D7,地址数据总线
E10ISA_RDn,数据读控制信号
E11ISA_WEn,数据写控制信号
E12ISA_ADVn,地址锁存控制信号
E13ISA_CSn,片选信号
F9GPIO24 / ISA_BCLK,同步时钟ISA_BCLK


ISA总线数据结构


  ISA总线的数据传输由ISA_BLOCK_INFO数据结构定义,其说明如下:


typedef struct
{
       PBYTE    pReadBuf;          
       PBYTE    pWriteBuf;         
       DWORD       dwDataLength;
       DWORD       dwDataPortOfs; 
       DWORD       dwInc;                
} ISA_BLOCK_INFO, *PISA_BLOCK_INFO;


  成员说明


  pReadBuf

  ISA总线读数据缓存,!=NULL表示为ISA总线读操作,pReadBuf与pWriteBuf指针不能同时有效


  pWriteBuf

  ISA总线写数据缓存, !=NULL表示为ISA总线写操作,pReadBuf与pWriteBuf指针不能同时有效


  dwDataLength

  读/写访问的字节长度


  dwDataPortOfs

  ISA端口地址。ISA总线驱动程序支持应用程序使用不同的地址范围来区别不同的操作模式,根据ISA端口地址可实现以下几种总线访问模式。


ValueISA总线访问模式
0x0000...0x00FF异步总线周期,CPU操作
0x1000...0x10FF异步总线周期,DMA方式传输,dwInc需要同时设置为0
0x4000...0x40FF同步总线周期,DMA方式传输,dwInc需要同时设置为0


  由于精简ISA总线的宽度只有8-bit,因此它实际的物理寻址范围也只有8-bit,即0x00-0xFF。


  dwInc

  每次ISA读写后地址是否增加


ValueMeaning
0固定地址
1读/写后地址自动加1


  备注:

  ISA总线的CPU操作方式主要用于小量数据的传输,比如控制命令、配置数据读写等。

  DMA方式传输可在较低CPU开销的情况下实现大批量数据的高速传输,DMA方式传输要求传输的数据长度dwDataLength为32的整倍数、同时需要将dwInc设置为0,否则驱动程序会使用CPU方式进行数据传输。DMA传输方式需要连接在ISA总线的上CPLD/FPGA使用固定的数据端口地址,在CPLD/FPGA内部自己管理地址偏移。

  ESM335x工控主板只支持异步访问模式,在异步DMA方式传输时,需要将dwInc设置为0来固定数据端口地址。

  ESM6802/ESM7000支持异步和同步DMA访问模式,但由于SDMA(NXP Smart DMA)的原因,即使将dwInc设置为0,在进行DMA数据传输时也无法固定端口地址,因此CPLD/FPGA需要通过额外的方法来识别当前为DMA传输。ESM6802/ESM7000在进行异步DMA访问时,可通过1位GPIO来向外表明当前为异步DMA访问模式,也可通过CPU操作方式向指定地址写入特定命令,让CPLD/FPGA切换到异步DMA传输模式。在进行同步DMA访问时,可结合ISA_BCLK信号来识别当前的传输模式。为了避免混淆,在实际应用时建议仅使用一种DMA(同步或异步)访问模式,推荐使用同步DMA访问模式以获得最高的ISA总线速度。


ISA总线速度测试


  在ISA_BLOCK_INFO结构体中定义了异步CPU访问,异步DMA和同步DMA三种ISA总线访问方式,而异步CPU访问又可通过驱动API函数和内存映射两种方式来实现。


  驱动API访问是指应用程序直接调用设备驱动API函数WriteFil/ReadFile进行字/字节的总线读写。ISA总线周期通常在200ns左右,而应用程序调用一次设备驱动程序API花费的时间却需要数微秒的时间,这显然大大降低了总线单字(或单字节)的访问效率。为了解决这一问题,我们利用了WinCE的虚拟地址映射技术,在ISA驱动程序中实现了,在使用ISA总线的应用进程地址空间内分配一段虚拟地址空间,并将其与ISA接口的物理地址空间进行绑定。简单来讲就是实现了在WinCE应用程序中可以直接访问ISA总线的外设地址空间,即内存映射访问方式。


  下图是ESM3354、ESM7000和ESM6802,ISA总线在各种访问模式下的总线速度。


精简ISA总线WinCE编程.png

图1 ESM335x / ESM7000 / ESM6802 ISA总线速度


  下图是ESM7000 ISA总线在不同访问模式下,总线速度与CPU使用情况的对比。可以看到内存映射方式访问(异步CPU操作)与异步DMA访问在总线速度上很接近,但使用DMA传输可大大降低对CPU的开销。因此对于频繁的字或字节访问可以使用内存映射方式访问,而对于成批量数据的读写应该使用DMA方式进行传输。


精简ISA总线WinCE编程.png

图2 ESM7000 ISA总线在不同访问方式下总线速度与CPU占用率


ISA总线访问API


  对ISA总线的所有访问方法都包含在isa_api_v3.cpp文件中,如下所示:


#include "StdAfx.h"
#include "bsp_drivers.h"
#include <WinIoCtl.h>
#include "isa_api_v3.h"
 
DWORD       g_dwISABase = 0;
 
HANDLE       ISA_Open( LPCWSTR lpDevName )
{
       HANDLE       hISA;
 
       hISA = CreateFile(lpDevName,           // name of device
              GENERIC_READ|GENERIC_WRITE,         // desired access
              FILE_SHARE_READ|FILE_SHARE_WRITE,   // sharing mode
              NULL,                               // security attributes (ignored)
              OPEN_EXISTING,                      // creation disposition
              FILE_FLAG_RANDOM_ACCESS,            // flags/attributes
              NULL); 
 
       if(hISA != INVALID_HANDLE_VALUE)
       {
              if(DeviceIoControl(hISA,         //打开“ISA1:”返回的Handler
              IOCTL_VIRTUAL_COPY_EX,      // IOCTL命令码    
              NULL,0,                                     // 不使用输入参数
              &g_dwISABase, sizeof(DWORD),  // 得到ISA基地址   
              NULL, NULL))
                     return    hISA;
      
              CloseHandle(hISA);
              hISA = INVALID_HANDLE_VALUE;
       }     
       return    hISA;
}
 
BOOL     ISA_Close( HANDLE hISA)
{
       g_dwISABase = 0;
       return CloseHandle( hISA );
}
 
// Function: read a byte from a port on ISA bus
// Input: hISA: the HANDLE returned from ISA_Open function
//        dwPortOffset = 0, 1, .. 255, address of port on ISA
// Return: the byte data read
BYTE ISARead8(HANDLE hISA, DWORD dwPortOffset)
{
       WORD   *pPortAddr;
       WORD   wValue;
       BYTE      ucValue;
       DWORD       dwNbBytesRead = 0;
 
       if(g_dwISABase != 0)
       {
              dwPortOffset &= 0xff;
              dwPortOffset <<= 1;               // D[0..7] <=> A[1..8] in AD-muxed mode
              pPortAddr = (WORD*)(g_dwISABase + dwPortOffset);
              wValue = *pPortAddr;
 
              return (BYTE)wValue;
       }     
 
       ucValue = (BYTE)(dwPortOffset & 0xFF);
       ReadFile(hISA, &ucValue, sizeof(ucValue), &dwNbBytesRead, NULL);
       return ucValue;
}
 
// Function: write a byte to a port on ISA bus
// Input: hISA: the HANDLE returned from ISA_Open function
//        dwPortOffset = 0, 1, .. 255, address of port on ISA
//        ucValue = the byte data to be written
void ISAWrite8(HANDLE hISA, DWORD dwPortOffset, BYTE ucValue)
{
       WORD   *pPortAddr;
       WORD   wValue;
       DWORD       dwNbBytesWritten = 0;
 
       if(g_dwISABase != 0)
       {
              dwPortOffset &= 0xff;
              dwPortOffset <<= 1;               // D[0..7] <=> A[1..8] in AD-muxed mode
              pPortAddr = (WORD*)(g_dwISABase + dwPortOffset);
              *pPortAddr = (WORD)ucValue;   
              return;
       }     
 
       wValue = (WORD)(dwPortOffset & 0xFF);
       wValue = (wValue << 8) | ucValue;
       WriteFile(hISA, &wValue, sizeof(wValue), &dwNbBytesWritten, NULL);
}
 
// Function: read a word from a port on ISA bus
// Input: hISA: the HANDLE returned from ISA_Open function
//        dwPortOffset = 0, 2, 4, .. 254, address of port on ISA
// Return: the word data read
WORD ISARead16(HANDLE hISA, DWORD dwPortOffset)
{
       DWORD       *pPortAddr;
       DWORD       dwValue;
       WORD   wValue;
       DWORD       dwNbBytesRead = 0;
 
       if(g_dwISABase != 0)
       {
              dwPortOffset &= 0xFE;           // 2-byte alignment
              dwPortOffset <<= 1;               // D[0..7] <=> A[1..8] in AD-muxed mode
              pPortAddr = (DWORD*)(g_dwISABase + dwPortOffset);
              dwValue = *pPortAddr;
              // the high-byte of data is at value[23..16]
              return (WORD)(((dwValue >> 8) & 0xFF00) | (dwValue & 0xFF));
       }
 
       wValue = (WORD)(dwPortOffset & 0xFE);
       ReadFile(hISA, &wValue, sizeof(wValue), &dwNbBytesRead, NULL);      
       return wValue;
}
 
// Function: write a word to a port on ISA bus
// Input: hISA: the HANDLE returned from ISA_Open function
//        dwPortOffset = 0, 2, 4, .. 254, address of port on ISA
//        wValue = the word data to be written
void ISAWrite16(HANDLE hISA, DWORD dwPortOffset, WORD wValue)
{
       DWORD       *pPortAddr;
       DWORD       dwValue;
       DWORD       dwNbBytesWritten = 0;
 
       if(g_dwISABase != 0)
       {
              dwPortOffset &= 0xFE;           // 2-byte alignment
              dwPortOffset <<= 1;               // D[0..7] <=> A[1..8] in AD-muxed mode
              pPortAddr = (DWORD*)(g_dwISABase + dwPortOffset);
              dwValue = wValue;
              // dispatch high-byte of data to value[23..16]
              *pPortAddr = ((dwValue << 8) & 0x00ff0000) | (dwValue & 0x000000ff);
              return;
       }
 
       dwValue = (dwPortOffset << 16) | wValue;
       WriteFile(hISA, &dwValue, sizeof(dwValue), &dwNbBytesWritten, NULL);
}
 
// Function: read a bulk data from a port on ISA bus
// Input: hISA: the HANDLE returned from ISA_Open function
//        *tp: a pointer to the ISA_BLOCK_INFO structure
// Return: number of bytes read
DWORD       ISAReadBuf(HANDLE hISA, PISA_BLOCK_INFO tp)
{
       DWORD dwNbBytesRead = 0;
 
       if(ReadFile(hISA, tp, sizeof(ISA_BLOCK_INFO), &dwNbBytesRead, NULL))
              return dwNbBytesRead;
       return 0;
}
 
// Function: read a bulk data from a port on ISA bus
// Input: hISA: the HANDLE returned from ISA_Open function
//        *tp: a pointer to the ISA_BLOCK_INFO structure
// Return: the number of bytes written
DWORD       ISAWriteBuf(HANDLE hISA, PISA_BLOCK_INFO tp)
{
       DWORD dwNbBytesWritten = 0;
       if(WriteFile(hISA, tp, sizeof(ISA_BLOCK_INFO), &dwNbBytesWritten, NULL))
              return dwNbBytesWritten;
 
       return 0;
}


  注意,当采用内存映射方式访问时,若在多个线程中均有调用字或字节读写函数(ISAWrite8, ISAWrite16, ISARead8, ISARead126),需要加互斥锁,以保证对硬件资源操作的完整性。通过驱动API调用的读写函数,在驱动内部已经增加了互斥操作,应用程序不需要再次添加。


ISA异步总线周期读时序


精简ISA总线WinCE编程.png


ISA异步总线周期写时序


精简ISA总线WinCE编程.png


ISA同步总线周期读时序


精简ISA总线WinCE编程.png


  ISA扩展单元可根据上述时序来支持高速的同步总线周期读逻辑电路,其要点包括:

  ● 每个同步周期共12个BCLK时钟,第1个BCLK下降沿ADVn有效,标志同步周期的开始,之后连续11个BCLK下降沿后同步周期结束。

  ● 每个同步周期传输8个字节有效数据,ISA总线从BCLK第5个上升沿开始锁存数据,连续读8个字节。

  ● ISA总线虽然输出了ADDR地址数据,但这个地址是不固定的,ISA扩展单元应该忽略此地址数据。


ISA同步总线周期写时序


精简ISA总线WinCE编程.png


  ISA扩展单元可根据上述时序来支持高速的同步总线周期写逻辑电路,其要点包括:

  ● 每个同步周期共12个BCLK时钟,第1个BCLK下降沿ADVn有效,标志同步周期的开始,之后连续11个BCLK下降沿后同步周期结束。

  ● 每个同步周期传输8个字节有效数据,ISA扩展单元应从BCLK第5个上升沿开始锁存数据,连续8个字节。

  ● ISA总线虽然输出了ADDR地址数据,但这个地址是不固定的,ISA扩展单元应该忽略此地址数据。