精简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 #include #include #include #include #include #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 *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。
-
Linux
+关注
关注
87文章
11304浏览量
209498 -
嵌入式主板
+关注
关注
7文章
6085浏览量
35336
发布评论请先 登录
相关推荐
评论