精简ISA总线Linux编程 – Part1

 2019-3-20     作者:Emtronix     [nemail]    
[lablebox]

  精简ISA总线接口是一种8-bit宽度的双向并行扩展总线,其特点是地址数据分时复用8-bit总线,加上4条总线控制信号,即可实现对外部数据的快速读写。若再使能一条总线时钟信号(共13条信号),就可实现高达10MB/s的数据传输。精简ISA总线作为英创主板的特色功能之一,在ESM6802、ESM7000、ESM7100、ESM335x等多款型号中均有配置。


  对精简ISA总线接口的应用编程,将通过3个部分分别加以介绍。本文是第一部分,主要介绍ISA总线的访问模式,以及最基本的总线数据读写。第二部分介绍由应用程序启动基于DMA的数据块读写。采用DMA方式实现数据库读写,可有效降低CPU的负载率。第三部分介绍ISA扩展硬件触发DMA数据传输的方法,该方法主要面向工业控制领域的高速数据采集。由于采用硬件触发的DMA传输机制,使得前端的高速AD单元不再采用昂贵的高速SRAM做数据缓冲,从而使采集单元的成本大幅度下降。该方法可实现高达5MB/s(每秒5兆字节)以上的数据采集率。


  ISA总线信号定义如下:


信号及说明PIN#信号及说明
RESET_B,硬件复位12ISA_ADVn,地址锁存控制信号
ISA_AD0,地址数据总线,LSB34ISA_AD4,地址数据总线
ISA_AD1,地址数据总线56ISA_AD5,地址数据总线
ISA_AD2,地址数据总线78ISA_AD6,地址数据总线
ISA_AD3,地址数据总线910ISA_AD7,地址数据总线,MSB
MSLn,支持多模块挂接总线1112ISA_WEn,数据写控制信号
GPIO9,可选作为IRQ1314ISA_RDn,数据读控制信号
GPIO8,可选作为IRQ1516ISA_CSn,片选控制信号
GPIO25,可选作为IRQ1718VDD_5V0,+5V供电
GPIO24 / ISA_BCLK,同步时钟ISA_BCLK1920GND,电源信号地


  本文以下部分,将以ESM7000 Linux平台为例,介绍具体的编程方法。


ISA总线操作模式


  精简ISA总线的宽度只有8-bit,因此它的地址寻址范围也只有8-bit,即0x00 – 0xFF。ISA总线的驱动程序支持应用程序使用不同的地址范围,来区别不同的访问类型。根据ISA地址可有如下6种类型:


模式offsetISA总线操作
00x0000 … 0x00FF异步总线周期,CPU操作
10x1000 … 0x10FF异步总线周期,MemCpy方式DMA,固定端口地址
20x2000 … 0x20FF异步总线周期,外部信号触发方式DMA,固定端口地址
30x3000 … 0x30FF同步总线周期,CPU操作,要求ISA端口地址16字节对齐
40x4000 … 0x40FF同步总线周期,MemCpy方式DMA,同步译码端口
50x5000 … 0x50FF同步总线周期,外部信号触发方式DMA,同步译码端口


  ISA总线的异步总线周期是指每个总线周期读写一个字节,只需用到ISA最基本的12条信号线;而同步总线周期则需要使用总线时钟信号(共13条信号线),可实现一个总线周期读写4个字节。在《在英创工控主板上实现高速工控数据采集》一文中有对异步总线周期时序和同步总线周期时序较详细的介绍。


  由iMX7DSDMA(Smart DMA)控制器特性决定,ESM7000可支持的ISA总线操作类型如下:


模式offsetISA总线读操作
00x0000 … 0x00FF异步总线周期,CPU读,inc仅对本模式有效
30x3000 … 0x30FF同步总线周期,CPU读,要求ISA端口地址16字节对齐
40x4000 … 0x40FF同步总线周期,MemCpy方式DMA读,同步译码端口
50x5000 … 0x50FF同步总线周期,外部信号触发方式DMA读,同步译码端口


模式offsetISA总线写操作
00x0000 … 0x00FF异步总线周期,CPU写,inc仅对本模式有效
30x3000 … 0x30FF同步总线周期,CPU写,要求ISA端口地址16字节对齐
40x4000 … 0x40FF同步总线周期,MemCpy方式DMA写,同步译码端口


  同步总线周期主要用于数据的高速传送,因此采用固定数据端口。数据端口的译码则采用直接识别同步总线周期的方法,而不再采用对总线地址译码的方法。异步总线周期则主要用于扩展电路单元内各寄存器的控制。


  采用DMA进行ISA总线数据传送的目的,是为了降低高速传送大量数据时的CPU开销,使系统在进行高速数据采集的同时,还能进行其他的必要操作。MemCpy方式的DMA是指软件线程启动DMA,然后该线程挂起等待DMA操作完成。在多线程环境中,其他线程即可在DMA执行过程中得以并行运行。MemCpy方式DMA的具体情况将在《精简ISA总线编程– Part 2》中介绍。硬件触发DMA,为低成本实现高速数据采集提供了技术手段,将在《精简ISA总线编程– Part 3》中介绍相关的采集时序和程序实现。


  表格中的offset项,是指在程序设计中使用的数据结构struct isa_transfer的成员,其结构如下:


structisa_transfer
{
       void              *rx_buf;                /* != NULL: buffer for bus read */
       void              *tx_buf;                /* != NULL: buffer for bus write */
       unsigned     len;                      /* buffer length in byte */
       unsigned     offset;                  /* offset,port address on isa bus */
       unsigned     inc;                      /* = 0: fixed offset, = 1: offset+1 after r/w */
};



  每一个总线周期的操作只能是读或写,因此在isa_transfer结构中只能有一个buffer指针不为NULL。


ISA总线访问API


  对ISA总线硬件端口的基本访问方法,包含在isa_api_v3.cpp文件中,如下所示:


#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/mman.h>
#include<unistd.h>
#include"em335x_drivers.h"
 
unsignedchar *isa_base = NULL;
unsignedintmap_size = 4096;
 
// return >= 0: return file handle
// return < 0: return failed code
intisa_open()
{
       intfd;
 
       fd = open("/dev/em_isa", O_RDWR);
       if(fd< 0)
       {
              returnfd;
       }
 
       isa_base = (unsignedchar*)mmap(0,map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
       if (isa_base == MAP_FAILED) {
              printf("%s mmap failed\n", __func__);
              isa_base = NULL;
              close(fd);
              return -1;
       }
 
       returnfd;
}
 
intisa_close(intfd)
{
       if(isa_base != NULL) {
              munmap(isa_base, map_size);
              isa_base = NULL;
       }
 
       returnclose(fd);
}
 
// offset: port address in isa bus
unsignedcharisa_read(intfd, unsignedint offset)
{
       unsignedcharval_b;
       int          rc;
 
       offset&= 0xff;
       if(isa_base != NULL) {
              unsignedshortintval_w;
 
              val_w = *((unsignedshortint*)(isa_base + (offset << 1)));
              val_b = (unsignedchar)(val_w& 0xff);
              returnval_b;
       }
 
       val_b = offset;
       rc = read(fd, &val_b, sizeof(unsignedchar));
       if(rc< 0)
       {
              // read error
              printf("%s failed %d\n", __func__, rc);
       }
 
       returnval_b;
}
 
// offset: port address in isa bus
voidisa_write(intfd, unsignedint offset, unsignedcharval_b)
{
       unsignedshortintval_w;
       int                 rc;
 
       offset&= 0xff;
       if(isa_base != NULL) {
              val_w = val_b;
              *((unsignedshortint*)(isa_base + (offset << 1))) = val_w;
              return;
       }
 
       val_w = ((offset & 0xff) << 8) | val_b;
       rc = write(fd, &val_w, sizeof(unsignedshortint));
       if(rc< 0)
       {
              // read error
              printf("%s failed %d\n", __func__, rc);
       }
}
 
// offset: port address in isa bus
unsignedshortintisa_read16(intfd, unsignedint offset)
{
       unsignedshortintval_w;
       int                 rc;
 
       // 2-byte alignment is required for offset
       offset&= 0xfe;
 
       if(isa_base != NULL) {
              unsignedintval;
              val = *((unsignedint*)(isa_base + (offset << 1)));
              val_w = (unsignedshortint)((val>> 8) | (val& 0x00ff));
              returnval_w;
       }
 
       val_w = (unsignedshortint)offset;
       rc = read(fd, &val_w, sizeof(unsignedshortint));
       if(rc< 0)
       {
              // read error
              printf("%s failed %d\n", __func__, rc);
       }
 
       returnval_w;
}
 
// offset: port address in isa bus
voidisa_write16(intfd, unsignedint offset, unsignedshortintval_w)
{
       unsignedintval;
       intrc;
 
       // 2-byte alignment is required for offset
       offset&= 0xfe;
 
       if(isa_base != NULL) {
              val = val_w;
              val = ((val<< 8) & 0x00ff0000) | (val& 0x000000ff);
              *((unsignedint*)(isa_base + (offset << 1))) = val;
              return;
       }
 
       val = (offset << 16) | val_w;
       rc = write(fd, &val, sizeof(unsignedint));
       if(rc< 0)
       {
              // read error
              printf("%s failed %d\n", __func__, rc);
       }
}
 
intisa_read_buf(intfd, structisa_transfer *tp)
{
       int   rc;
 
       rc = read(fd, tp, sizeof(structisa_transfer));
       if(rc< 0)
       {
              // read error
              printf("%s failed %d\n", __func__, rc);
       }
       returnrc;
}
 
intisa_write_buf(intfd, structisa_transfer *tp)
{
       int   rc;
 
       rc = write(fd, tp, sizeof(structisa_transfer));
       if(rc< 0)
       {
              // read error
              printf("%s failed %d\n", __func__, rc);
       }
 
       returnrc;
}



  函数isa_open(..)和isa_close(..)是打开关闭ISA驱动的设备节点。为了提高应用程序访问ISA硬件寄存器的速度,ISA驱动实现了mmap功能,使isa_read(..)、isa_write(..)、isa_read16(..)和isa_write16(..)这4个函数可在用户空间直接操作ISA总线上的硬件寄存器,大大提高了代码的执行速度。注意若在多个线程中均有调用isa_api_v3.cpp的函数时,需要加互斥锁,保证对硬件资源操作的完整性。


CPU字节/字读写


  函数isa_write(fd, 0x40, 0x55)是向地址为0x40的寄存器写入单个字节0x55,对应的总线时序为:


1.png


  时序图显示了8位总线的地址/数据复用,地址需利用ADV#信号锁存。


  函数isa_read(fd, 0x40)是从地址为0x40的寄存器读取单个字节,对应的总线时序为:


2.png

 

  在上述时序中,ISA总线悬空,没有接具体的外围设备,因此由于分布电容的作用,在数据时段总线继续保持前面输出的地址0x40,读取的数据也会是0x40。若此时有外围设备输出数据至总线上,该数据则会被系统读取。


  函数isa_write16(fd, 0x40, 0xaa55)是向地址为0x40的字寄存器写入16-bit字0xaa55,对应的总线时序为:


3.png


  从时序图可见,16-bit数据写被分成两个连续的总线周期,其中低位字节0x55写入地址0x40寄存器,高位字节0xaa写入地址0x41寄存器。为了尽可能缩短2个周期的间隔,要求地址必须是偶对齐。


  函数isa_read16(fd, 0x40)是从地址为0x40的字寄存器读取16-bit字,对应的总线时序为:


4.png


  同样由于悬空总线的分布电容的作用,读取的数据会是0x4140。


CPU数据块读写


  为了提高数据传输速度,对数据块操作,推荐采用同步总线周期。在ESM7000 Linux版本中,每个同步总线周期可传送4个字节。为了进一步加快数据块的传送,在驱动中使用了ARMv7的汇编块指令来支持16字节以上的数据传输。因此强烈建议数据块传输长度选择为16字节的整倍数,其数据端口占用16个ISA地址,即只对高4位地址译码。


  进行数据块读写需要用到structisa_transfer传递相关参数。以下是执行32字节数据块写的代码,写入地址为0x3040。顺序的数据可方便时序的观察。


unsignedchargbuf[64 * 1024];
unsignedint i, value;
structisa_transfer      t;
unsignedchar   *pBuf8;
 
// write data block
memset(&t, 0, sizeof(structisa_transfer));
t.offset = 0x3040;
t.len = 32;
t.tx_buf = gbuf;
// fill data
value = 0x55;             // initial value
pBuf8 = (unsignedchar*)t.tx_buf;
for(i = 0; i<t.len; i++){
       *pBuf8 = (unsignedchar)(value + i);
       pBuf8++;
}
isa_write_buf(fd, &t);


  对应的总线时序说明如下:


5.png


  上图可见,32个字节分成了8个总线周期完成,大致的总线速率为13MB/s。展开上述时序可看到:


6.png


7.png


  总线上的数据0x55、0x59、…... 0x6D、0x71是每个总线周期CPU送出的第一个数据,保持3个时钟节拍,所以可在示波器中显示出来。进一步展开可看到:


8.png


9.png


10.png


11.png


12.png


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

  ● 每个周期的第一个BCLK下降沿ADV#有效,标志同步周期的开始,之后连续7个BCLK下降沿后同步周期结束。

  ● 第一个总线周期地址为输入地址,之后每个总线周期的地址+4,按16模循环。因此要求数据端口占用16个ISA地址。

  ● BCLK频率30MHz,从第5个上升沿开始锁存数据,连续4个数据。没有BCLK时输出的数据没有意义,不影响正常的数据传输。


  以上是对精简ISA总线基本读写的介绍,有兴趣的客户可与英创公司技术联系,索取测试代码源码。技术支持邮箱:support@emtronix.com

[lablebox]