精简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,硬件复位 | 1 | 2 | ISA_ADVn,地址锁存控制信号 |
ISA_AD0,地址数据总线,LSB | 3 | 4 | ISA_AD4,地址数据总线 |
ISA_AD1,地址数据总线 | 5 | 6 | ISA_AD5,地址数据总线 |
ISA_AD2,地址数据总线 | 7 | 8 | ISA_AD6,地址数据总线 |
ISA_AD3,地址数据总线 | 9 | 10 | ISA_AD7,地址数据总线,MSB |
MSLn,支持多模块挂接总线 | 11 | 12 | ISA_WEn,数据写控制信号 |
GPIO9,可选作为IRQ | 13 | 14 | ISA_RDn,数据读控制信号 |
GPIO8,可选作为IRQ | 15 | 16 | ISA_CSn,片选控制信号 |
GPIO25,可选作为IRQ | 17 | 18 | VDD_5V0,+5V供电 |
GPIO24 / ISA_BCLK,同步时钟ISA_BCLK | 19 | 20 | GND,电源信号地 |
本文以下部分,将以ESM7000 Linux平台为例,介绍具体的编程方法。
ISA总线操作模式
精简ISA总线的宽度只有8-bit,因此它的地址寻址范围也只有8-bit,即0x00 – 0xFF。ISA总线的驱动程序支持应用程序使用不同的地址范围,来区别不同的访问类型。根据ISA地址可有如下6种类型:
模式 | offset | ISA总线操作 |
0 | 0x0000 … 0x00FF | 异步总线周期,CPU操作 |
1 | 0x1000 … 0x10FF | 异步总线周期,MemCpy方式DMA,固定端口地址 |
2 | 0x2000 … 0x20FF | 异步总线周期,外部信号触发方式DMA,固定端口地址 |
3 | 0x3000 … 0x30FF | 同步总线周期,CPU操作,要求ISA端口地址16字节对齐 |
4 | 0x4000 … 0x40FF | 同步总线周期,MemCpy方式DMA,同步译码端口 |
5 | 0x5000 … 0x50FF | 同步总线周期,外部信号触发方式DMA,同步译码端口 |
ISA总线的异步总线周期是指每个总线周期读写一个字节,只需用到ISA最基本的12条信号线;而同步总线周期则需要使用总线时钟信号(共13条信号线),可实现一个总线周期读写4个字节。在《在英创工控主板上实现高速工控数据采集》一文中有对异步总线周期时序和同步总线周期时序较详细的介绍。
由iMX7DSDMA(Smart DMA)控制器特性决定,ESM7000可支持的ISA总线操作类型如下:
模式 | offset | ISA总线读操作 |
0 | 0x0000 … 0x00FF | 异步总线周期,CPU读,inc仅对本模式有效 |
3 | 0x3000 … 0x30FF | 同步总线周期,CPU读,要求ISA端口地址16字节对齐 |
4 | 0x4000 … 0x40FF | 同步总线周期,MemCpy方式DMA读,同步译码端口 |
5 | 0x5000 … 0x50FF | 同步总线周期,外部信号触发方式DMA读,同步译码端口 |
模式 | offset | ISA总线写操作 |
0 | 0x0000 … 0x00FF | 异步总线周期,CPU写,inc仅对本模式有效 |
3 | 0x3000 … 0x30FF | 同步总线周期,CPU写,要求ISA端口地址16字节对齐 |
4 | 0x4000 … 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,对应的总线时序为:
时序图显示了8位总线的地址/数据复用,地址需利用ADV#信号锁存。
函数isa_read(fd, 0x40)是从地址为0x40的寄存器读取单个字节,对应的总线时序为:
在上述时序中,ISA总线悬空,没有接具体的外围设备,因此由于分布电容的作用,在数据时段总线继续保持前面输出的地址0x40,读取的数据也会是0x40。若此时有外围设备输出数据至总线上,该数据则会被系统读取。
函数isa_write16(fd, 0x40, 0xaa55)是向地址为0x40的字寄存器写入16-bit字0xaa55,对应的总线时序为:
从时序图可见,16-bit数据写被分成两个连续的总线周期,其中低位字节0x55写入地址0x40寄存器,高位字节0xaa写入地址0x41寄存器。为了尽可能缩短2个周期的间隔,要求地址必须是偶对齐。
函数isa_read16(fd, 0x40)是从地址为0x40的字寄存器读取16-bit字,对应的总线时序为:
同样由于悬空总线的分布电容的作用,读取的数据会是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);
对应的总线时序说明如下:
上图可见,32个字节分成了8个总线周期完成,大致的总线速率为13MB/s。展开上述时序可看到:
总线上的数据0x55、0x59、…... 0x6D、0x71是每个总线周期CPU送出的第一个数据,保持3个时钟节拍,所以可在示波器中显示出来。进一步展开可看到:
ISA扩展单元可根据上述时序来支持高速的同步总线周期读写逻辑电路,其要点包括:
● 每个周期的第一个BCLK下降沿ADV#有效,标志同步周期的开始,之后连续7个BCLK下降沿后同步周期结束。
● 第一个总线周期地址为输入地址,之后每个总线周期的地址+4,按16模循环。因此要求数据端口占用16个ISA地址。
● BCLK频率30MHz,从第5个上升沿开始锁存数据,连续4个数据。没有BCLK时输出的数据没有意义,不影响正常的数据传输。
以上是对精简ISA总线基本读写的介绍,有兴趣的客户可与英创公司技术联系,索取测试代码源码。技术支持邮箱:support@emtronix.com。
成都英创信息技术有限公司 028-8618 0660