📄 io-port-programming.txt
字号:
器的速度, 以及系统的负荷), 就浪费掉 CPU 太多的时间, 因为 Linux 的排程器 (scheduler) (单就 x86 架构而言) 在将控制权发还给你的行程 (process) 之前 通常至少要花费 10-30 毫秒的时间. 因此, 短时间的延迟, 使用函式 usleep(3) 所得到的延迟结果通常会大於你在参数所指定的值, 大约至少有 10 ms. nanosleep() 在 Linux 2.0.x 一系列的核心发行版本中, 有一个新的系统呼叫 (system call), nanosleep() (请参考 nanosleep(2) 的说明文件), 他让你能够 休息或 延迟一个短的时间 (数微秒或更多). 如果延迟的时间 <= 2 ms, 若(且唯若)你执行中的行程 (process) 设定了软体的 即时 排程 (就是使用函式 tt/sched_setscheduler()/), 呼叫函式 nanosleep() 时 不是使用一个忙碌回圈来延迟时间; 就是会像函式 usleep() 一 样让出 CPU 的使用权休息去了. 这个忙碌回圈使用函式 udelay() (一个驱动程式常会用到的核心内部的函式) 来 达成, 并且使用 BogoMips 值 (BogoMips 可以准确量测这类忙碌回圈的速度) 来 计算回圈延迟的时间长度. 其如何动作的细节请参考 /usr/include/asm/delay.h). 使用 I/O 埠来延迟时间 另一个延迟数微秒的方法是使用 I/O 埠. 就是从埠位址 0x80 输入或输出任何 byte 的资料 (请参考前面) 等待的时间应该几乎只要 1 微秒这要看你的处理器 的型别与速度. 如果要延迟数微秒的时间你可以将这个动作多做几次. 在任何标 准的机器上输出资料到该 埠位址应该不会有不良的後果□对 (而且有些核心的设 备驱动程式也在使用他). {in|out}[bw]_p() 等函式就是使用这个方法来产生时 间延迟的 (请参考档案 asm/io.h). 实际上, 一个使用到埠位址□围为 0-0x3ff 的 I/O 埠指令几乎只要 1 微秒的时 间, 所以如果你要如此做, 例如, 直接使用并列埠, 只要加上几个 inb() 函式从 该 埠位址□围读入 byte 的资料即可. 使用组合语言来延迟时间 如果你知道执行程式所在机器的处理器型别与时钟速度, 你可以执行某些组合语 言指令以便获得较短的延迟时间 (但是记住, 你在执行中的行程 (process) 随时 会被暂停, 所以有时延迟的时间会比实际长). 如下面的表格所示, 内部处理器的 速度决定了所要使用的时钟周期数; 如, 一个 50 MHz 的处理器 (486DX-50 或 486DX2-50), 一个时钟周期要花费 1/50000000 秒 (=200 奈秒). 指令 i386 时钟周期数 i486 时钟周期数nop 3 1xchg %ax,%ax 3 3or %ax,%ax 2 1mov %ax,%ax 2 1add %ax,0 2 1 (对不起, 我不知道 Pentiums 的资料, 或许与 i486 接近吧. 我无法在 i386 的 资料上找到只花费一个时钟周期的指令. 如果能够就请使用花费一个时钟周期的 指令, 要不然就使用管线技术的新式处理器也是可以缩短时间的.) 上面的表格中指令 nop 与 xchg 应该不会有不良的後果. 指令最後可能会 改变 旗号暂存器的内容, 但是这没关系因为 gcc 会处理. 指令 nop 是个好的选择. 想要在你的程式中使用到这些指令, 你得使用 asm("instruction"). 指令的语法 就如同上面表格的用法; 如果你想要在单一的 asm() 叙述中使用多个指令, 可以 使用分号将他们隔开. 例如, asm("nop ; nop ; nop ; nop") 会执行四个 nop 指令, 在 i486 或 Pentium 处理器中会延迟四个时钟周期 (或是 i386 会延迟 12 个时钟周期). gcc 会将 asm() 翻译成单行组合语言程式码, 所以不会有呼叫函式的负荷. 在 Intel x86 架构中不可能有比一个时钟周期还短的时间延迟. 在 Pentiums 处理器上使用函式 rdtsc 对於 Pentiums 处理器而言, 你可以使用下面的 C 语言程式码来取得自从上次重 新开机 到现在经过了多少个时钟周期: ______________________________________________________________ extern __inline__ unsigned long long int rdtsc() { unsigned long long int x; __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); return x; } ______________________________________________________________ 你可以询问参考此值以便延迟你想要的时钟周期数. 4.2 时间的量测 想要时间精确到一秒钟, 使用函式 time() 或许是最简单的方法. 想要时间更精 确, 函式 gettimeofday() 大约可以精确到微秒 (但是如前所述会受到 CPU 排程 的影响). 至於 Pentiums 处理器, 使用上面的程式码片断就可以精确到一个时钟 周期. 如果你要你执行中的行程 (process) 在一段时间到了之後能够被通知 (get a signal), 你得使用函式 setitimer() 或 alarm() . 细节请参考函式的使用说明 文件. 5. 使用其他程式语言 上面的说明集中在 C 程式语言. 他应该可以直接应用在 C++ 及 Objective C 之 上. 至於组合语言部分, 虽然你必须先在 C 语言中呼叫函式 ioperm() 或 iopl() , 但是之後你就可以直接使用 I/O 埠读写指令. 至於其他程式语言, 除非你可以在该程式语言中插入单行组合语言或 C 语言之程 式码或者使用上面所说的系统呼叫, 否则倒不如撰写一个内含有存取 I/O 埠或延 迟时间所必需函式之简单的 C 原始程式码或许还比较容易, 编译之後再与你的程 式链结. 要不然就是使用前面所说的 /dev/port 字元装置档案. 6. 一些有用的 I/O 埠 本节提供一些常用 I/O 埠的程式撰写资讯这些都是可以直接拿来用的一般目的 TTL (或 CMOS) 逻辑位准的 I/O 埠. 如果你要按照其原始的设计目的来使用这些或其他常用的I/O 埠 (例如, 控制一 般的印表机或数据机), 你应该会使用现成的装置驱动程式 (他通常被含在核心 中) 而不会如本文所说地去撰写 I/O 埠程式. 本节主要是提供给那些想要将 LCD 显示器, 步进马达, 或是其他商业电子产品 连接到 PC 标准 I/O 埠的人. 如果你想要控制大众市场所贩卖的装置像是扫描器 (已经在市场贩卖了一段期 间), 去找看看是否有现成的 Linux 装置驱动程式. 网页 [4]Hardware-HOWTO 是 个好的参考起点. 至於想要知道更多有关如何连接电子装置到电脑(以及一般的电子学原理)的相关 资讯则网页 [5]http://www.hut.fi/Misc/Electronics/ 是个好的资料来源. 6.1 并列埠 (parallel port) 并列埠的基本埠位址 (以下称之为 ``BASE'') 之於 /dev/lp0 是 0x3bc , 之於 /dev/lp1 是 0x378 , 之於 /dev/lp2 是 0x278 . 如果你只是想要控制一些像是 一般印表机的动作, 可以参考网页 [6]Printing-HOWTO. 除了下面即将描述的标准仅输出 (output-only) 模式, 大多数的并列埠都有 `扩 充的' 双向 (bidirectional) 模式. 至於较新的 ECP/EPP 模式 (以及一般的 IEEE 1284 标准) 埠口的相关资料, 可以参考网页 [7]http://www.fapo.com/ 以 及 [8]http://www.senet.com.au/~cpeacock/parallel.htm. 因为在使用者模式 (user-mode) 中的程式无法使用 IRQs 或 DMA, 想要使用 ECP/EPP 模式你或许得 撰写一个核心的装置驱动程式; 我想应该有人写了这类的装置驱动程式, 但是详 情我并不知道. 埠位址 BASE+0 (资料埠) 用来控制资料埠的信号位准 (D0 到 D7 分别代表著 bits 0 到 7, 位准状态: 0 = 低位准 (0 V), 1 = 高位准 (5 V)). 一个写入资 料到该埠的动作会将资料信号位准拴住 (latches) 在埠的脚位 (pins) 上. 一个 将该埠的资料读出的动作会将上一次以标准仅输出 (output-only) 模式或扩充的 写入模式所拴住的资料信号位准读回, 或是以扩充读出模式 从另外一 个装置将 脚位上的资料信号位准读回. 埠位址 BASE+1 (状态埠) 是个仅读 (read-only) 的埠, 会将下面的输入信号位 准读回: * Bits 0 和 1 保留不用. * Bit 2 IRQ 的状态 (不是个脚位 (pin) , 我不知道他的工作原理) * Bit 3 ERROR (1=高位准) * Bit 4 SLCT (1=高位准) * Bit 5 PE (1=高位准) * Bit 6 ACK (1=高位准) * Bit 7 -BUSY (0=高位准) (我不确定高低位准的电压状态.) 埠位址 BASE+2 (控制埠) 是个仅写 (write-only) 的埠 (一个将该埠的资料读出 的动作仅会将上一次写入的资料信号位准读回), 用来控制下面的状态信号: * Bit 0 -STROBE (0=高位准) * Bit 1 AUTO_FD_XT (1=高位准) * Bit 2 -INIT (0=高位准) * Bit 3 SLCT_IN (1=高位准) * Bit 4 当被设定为 1 时允许并列埠产生 IRQ 信号 (发生在 ACK 脚位的位准 由低变高的瞬间) * Bit 5 用来控制扩充模式时埠的输出入方向 (0 = 写, 1 = 读), 这是个仅写 (write-only) 的埠 (一个将该埠的资料读出的动作对此 bit 一点用处也没 有). * Bits 6 and 7 保留不用. (同样地, 我不确定高低位准的电压状态.) 埠的脚位排列 (Pinout) 方式 (该埠是一个 25 只脚 D 字形外壳 (D-shell) 的 母头连接器) (i=输入, o=输出): 1io -STROBE, 2io D0, 3io D1, 4io D2, 5io D3, 6io D4, 7io D5, 8io D6,9io D7, 10i ACK, 11i -BUSY, 12i PE, 13i SLCT, 14o AUTO_FD_XT,15i ERROR, 16o -INIT, 17o SLCT_IN, 18-25 Ground IBM 的规格文件上说脚位 1, 14, 16, 和 17 (控制信号的输出) 采用电晶体的开 集极 (open collector) 驱动方式必需使用 4.7 仟欧姆 (kiloohm) 的提升电阻 接至 5 V 的电压 (可流入电流 20 mA, 流出电流 0.55 mA, 高位准的输出电压就 是 5.0 V 减去提升电阻的电压). 剩下来的脚位可流入电流 24 mA, 流出电流 15 mA, 高位准的输出电压最小 2.4 V. 低位准的输出电压二者都是最大 0.5 V. 那些非 IBM 规格的并列埠或许会偏离这个标准. 更多的相关资料请参考网页 [9]http://www.hut.fi/Misc/Electronics/circuits/lptpower.html. 最後, 给你一个警告: 留心接地的问题. 我曾经在电脑还是开机的状况就去连接 他因而 弄坏好几个并列埠. 发生了这种事情你可能会觉得还是不要将并列埠整 合到主机板里面比较好. (你通常可以拿一片便宜的标准 `multi-I/O' 卡安装第 二个 并列埠; 只要将其他不需要的埠停用, 然後将卡片上并列埠的埠位址设定在 空著的位址即可. 你不需在意并列埠的 IRQ 设定, 因为通常不会被用到.)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -