第11章 并行数据传输技术与8255A接口控制器的应用

第11章 并行数据传输技术与8255A接口控制器的应用

并行数据传输是计算机系统中的一种基本输入输出方式,相较于串行通信,它能同时传输多位数据,提高传输效率。本章将深入探讨并行接口技术,特别是8255A可编程并行接口芯片的原理、编程方法及其在各种场景中的应用实现。

11.1 可编程并行接口芯片8255A详解

11.1.1 8255A控制器的结构与引脚功能

8255A是Intel公司推出的一款经典可编程并行接口芯片,被广泛应用于各种微处理器系统中。它提供了24个可编程I/O引脚,可灵活配置为输入或输出模式,满足各种并行数据传输需求。

基本结构

8255A内部主要由以下部分组成:

三个8位并行端口:端口A(PA0-PA7)、端口B(PB0-PB7)和端口C(PC0-PC7)一个控制寄存器:用于配置三个端口的工作模式数据总线缓冲器:连接到系统数据总线读/写控制逻辑:处理CPU的读写操作控制寄存器:存储控制字,设定各端口的工作模式

下面是8255A的内部结构简化框图:

gherkin



    +-------------------------------------+
    |               8255A                 |
    |                                     |
    |  +--------+        +-----------+    |
    |  |数据总线|        |端口A(PA0-7)|    |
    |  |缓冲器  | <----> |           |    |
    |  +--------+        +-----------+    |
    |       ^                             |
    |       |            +-----------+    |
    |       |            |端口B(PB0-7)|    |
    |       | <--------> |           |    |
    |       |            +-----------+    |
    |       |                             |
    |       |            +-----------+    |
    |       |            |端口C(PC0-7)|    |
    |       | <--------> |           |    |
    |       |            +-----------+    |
    |       |                             |
    |       |            +-----------+    |
    |       |            |控制寄存器 |    |
    |       | <--------> |           |    |
    |       |            +-----------+    |
    |       |                             |
    |  +----+------+                      |
    |  |读/写控制  |                      |
    |  |  逻辑     |                      |
    |  +-----------+                      |
    |                                     |
    +-------------------------------------+

引脚功能

8255A采用40引脚DIP封装,主要引脚功能如下:

PA0-PA7(输入/输出):端口A的8个双向数据线PB0-PB7(输入/输出):端口B的8个双向数据线PC0-PC7(输入/输出):端口C的8个双向数据线D0-D7(输入/输出):与系统数据总线相连的8个双向数据线RESET(输入):芯片复位信号,高电平有效CS(输入):片选信号,低电平有效RD(输入):读控制信号,低电平有效WR(输入):写控制信号,低电平有效A0-A1(输入):地址输入,用于选择端口A/B/C或控制寄存器VCC:电源正极(+5V)GND:电源负极(接地)

以下是C语言模拟8255A基本结构的代码示例:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
 
// 定义8255A结构
typedef struct {
    // 三个端口的数据寄存器
    uint8_t port_a;
    uint8_t port_b;
    uint8_t port_c;
    
    // 控制寄存器
    uint8_t control;
    
    // 端口方向 (每位对应一个引脚,1表示输出,0表示输入)
    uint8_t port_a_direction;
    uint8_t port_b_direction;
    uint8_t port_c_upper_direction; // PC4-PC7
    uint8_t port_c_lower_direction; // PC0-PC3
    
    // 工作模式
    uint8_t group_a_mode; // 0: 基本I/O,1: 选通I/O
    uint8_t group_b_mode; // 0: 基本I/O
} PPI8255A;
 
// 初始化8255A
void init_8255a(PPI8255A *ppi) {
    ppi->port_a = 0;
    ppi->port_b = 0;
    ppi->port_c = 0;
    ppi->control = 0;
    
    // 默认所有端口为输入模式
    ppi->port_a_direction = 0;
    ppi->port_b_direction = 0;
    ppi->port_c_upper_direction = 0;
    ppi->port_c_lower_direction = 0;
    
    // 默认工作模式0
    ppi->group_a_mode = 0;
    ppi->group_b_mode = 0;
    
    printf("8255A 已初始化
");
}
 
// 模拟写操作
void write_8255a(PPI8255A *ppi, uint8_t address, uint8_t data) {
    switch (address & 0x03) {
        case 0: // 端口A
            if (ppi->port_a_direction) { // 如果是输出模式
                ppi->port_a = data;
                printf("写入端口A: 0x%02X
", data);
            } else {
                printf("错误: 端口A配置为输入模式,不能写入
");
            }
            break;
            
        case 1: // 端口B
            if (ppi->port_b_direction) { // 如果是输出模式
                ppi->port_b = data;
                printf("写入端口B: 0x%02X
", data);
            } else {
                printf("错误: 端口B配置为输入模式,不能写入
");
            }
            break;
            
        case 2: // 端口C
            {
                uint8_t old_value = ppi->port_c;
                uint8_t new_value = data;
                
                // 处理上半部分 (PC4-PC7)
                if (ppi->port_c_upper_direction) {
                    new_value = (new_value & 0xF0) | (old_value & 0x0F);
                } else {
                    new_value = (old_value & 0xF0) | (new_value & 0x0F);
                }
                
                // 处理下半部分 (PC0-PC3)
                if (ppi->port_c_lower_direction) {
                    new_value = (old_value & 0xF0) | (new_value & 0x0F);
                } else {
                    new_value = (new_value & 0xF0) | (old_value & 0x0F);
                }
                
                ppi->port_c = new_value;
                printf("写入端口C: 0x%02X
", new_value);
            }
            break;
            
        case 3: // 控制寄存器
            ppi->control = data;
            printf("写入控制寄存器: 0x%02X
", data);
            
            // 检查位7是否为1(模式设定标志)
            if (data & 0x80) {
                // 解析控制字
                ppi->port_a_direction = (data & 0x10) ? 1 : 0; // 1 = 输出
                ppi->port_b_direction = (data & 0x02) ? 1 : 0;
                ppi->port_c_upper_direction = (data & 0x08) ? 1 : 0;
                ppi->port_c_lower_direction = (data & 0x01) ? 1 : 0;
                
                ppi->group_a_mode = (data & 0x60) >> 5;
                ppi->group_b_mode = (data & 0x04) ? 1 : 0;
                
                printf("模式设定:
");
                printf("  组A模式: %d
", ppi->group_a_mode);
                printf("  组B模式: %d
", ppi->group_b_mode);
                printf("  端口A方向: %s
", ppi->port_a_direction ? "输出" : "输入");
                printf("  端口B方向: %s
", ppi->port_b_direction ? "输出" : "输入");
                printf("  端口C上半方向: %s
", ppi->port_c_upper_direction ? "输出" : "输入");
                printf("  端口C下半方向: %s
", ppi->port_c_lower_direction ? "输出" : "输入");
            } else {
                // 位设置/复位操作
                int bit_position = data & 0x07;
                bool set_bit = (data & 0x01) ? true : false;
                
                // 修改端口C相应位
                if (set_bit) {
                    ppi->port_c |= (1 << bit_position);
                } else {
                    ppi->port_c &= ~(1 << bit_position);
                }
                
                printf("位设置/复位: 位%d 设为 %d
", bit_position, set_bit ? 1 : 0);
            }
            break;
    }
}
 
// 模拟读操作
uint8_t read_8255a(PPI8255A *ppi, uint8_t address) {
    uint8_t data = 0;
    
    switch (address & 0x03) {
        case 0: // 端口A
            data = ppi->port_a;
            printf("读取端口A: 0x%02X
", data);
            break;
            
        case 1: // 端口B
            data = ppi->port_b;
            printf("读取端口B: 0x%02X
", data);
            break;
            
        case 2: // 端口C
            data = ppi->port_c;
            printf("读取端口C: 0x%02X
", data);
            break;
            
        case 3: // 控制寄存器
            data = ppi->control;
            printf("读取控制寄存器: 0x%02X
", data);
            break;
    }
    
    return data;
}
 
// 显示8255A状态
void print_8255a_status(PPI8255A *ppi) {
    printf("
8255A 当前状态:
");
    printf("端口A: 0x%02X (方向: %s)
", ppi->port_a, ppi->port_a_direction ? "输出" : "输入");
    printf("端口B: 0x%02X (方向: %s)
", ppi->port_b, ppi->port_b_direction ? "输出" : "输入");
    printf("端口C: 0x%02X (上半部分: %s, 下半部分: %s)
", 
           ppi->port_c, 
           ppi->port_c_upper_direction ? "输出" : "输入",
           ppi->port_c_lower_direction ? "输出" : "输入");
    printf("控制寄存器: 0x%02X
", ppi->control);
    printf("组A模式: %d
", ppi->group_a_mode);
    printf("组B模式: %d
", ppi->group_b_mode);
    printf("
");
}
 
int main() {
    // 创建并初始化8255A
    PPI8255A ppi;
    init_8255a(&ppi);
    
    // 配置8255A工作模式
    // 控制字: 10001001
    // 位7=1: 模式设定标志
    // 位6-5=00: 组A工作在模式0
    // 位4=1: 端口A为输出
    // 位3=0: 端口C上半部分为输入
    // 位2=0: 组B工作在模式0
    // 位1=0: 端口B为输入
    // 位0=1: 端口C下半部分为输出
    write_8255a(&ppi, 3, 0x89);
    
    // 查看初始状态
    print_8255a_status(&ppi);
    
    // 向端口A写入数据
    write_8255a(&ppi, 0, 0xAA);
    
    // 向端口B写入数据 (这应该会失败,因为端口B配置为输入)
    write_8255a(&ppi, 1, 0x55);
    
    // 向端口C写入数据
    write_8255a(&ppi, 2, 0x0F);
    
    // 再次查看状态
    print_8255a_status(&ppi);
    
    // 读取各个端口
    read_8255a(&ppi, 0); // 端口A
    read_8255a(&ppi, 1); // 端口B
    read_8255a(&ppi, 2); // 端口C
    
    // 使用位设置/复位功能改变端口C的位1
    write_8255a(&ppi, 3, 0x01); // 设置位0
    write_8255a(&ppi, 3, 0x03); // 设置位1
    
    // 最终状态
    print_8255a_status(&ppi);
    
    return 0;
}

这段代码模拟了8255A的基本结构和操作,包括初始化、端口读写以及控制字配置。在实际硬件中,8255A会通过I/O端口或内存映射方式与CPU通信,但上述模拟代码展示了其内部工作原理。

11.1.2 8255A的工作模式分析

8255A支持三种不同的工作模式,可以满足各种输入输出需求:

模式0(基本输入/输出模式)

最简单的I/O模式,无需握手信号所有端口都可以配置为输入或输出输出锁存,输入不锁存适合简单的数字I/O应用

模式1(选通输入/输出模式)

带有握手信号的数据传输端口A和端口B可以配置为输入或输出端口C提供控制和状态信号适合与外设进行握手通信

模式2(双向总线模式)

仅端口A可以工作在此模式提供双向数据传输功能自动握手控制适合需要双向通信的场合

控制字格式

8255A的控制字决定了各个端口的工作模式和方向,其格式如下:

gherkin



位 7   6   5   4   3   2   1   0
   |   |   |   |   |   |   |   |
   |   +---+   |   |   |   |   +--- 端口C(低)方向 (1=输出, 0=输入)
   |       |   |   |   |   |
   |       |   |   |   |   +------- 端口B方向 (1=输出, 0=输入)
   |       |   |   |   |
   |       |   |   |   +----------- 组B模式选择 (0=模式0, 1=模式1)
   |       |   |   |
   |       |   |   +--------------- 端口C(高)方向 (1=输出, 0=输入)
   |       |   |
   |       |   +------------------- 端口A方向 (1=输出, 0=输入)
   |       |
   |       +----------------------- 组A模式选择 (00=模式0, 01=模式1, 1X=模式2)
   |
   +------------------------------- 模式标志位 (必须为1)

下面是不同工作模式配置的示例:

c



#include <stdio.h>
#include <stdint.h>
 
// 8255A端口地址(假设)
#define PORT_A      0x60
#define PORT_B      0x61
#define PORT_C      0x62
#define CONTROL     0x63
 
// 模拟outb函数
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%02X: 0x%02X
", port, value);
}
 
// 模拟inb函数
uint8_t inb(uint16_t port) {
    printf("读端口 0x%02X
", port);
    return 0xFF;  // 假设返回所有位为1
}
 
// 配置为模式0
void configure_mode0(bool port_a_output, bool port_b_output, 
                    bool port_c_upper_output, bool port_c_lower_output) {
    uint8_t control = 0x80;  // 位7必须为1
    
    if (port_a_output)
        control |= 0x10;
        
    if (port_b_output)
        control |= 0x02;
        
    if (port_c_upper_output)
        control |= 0x08;
        
    if (port_c_lower_output)
        control |= 0x01;
    
    printf("配置8255A为模式0:
");
    printf("  端口A: %s
", port_a_output ? "输出" : "输入");
    printf("  端口B: %s
", port_b_output ? "输出" : "输入");
    printf("  端口C上半部分: %s
", port_c_upper_output ? "输出" : "输入");
    printf("  端口C下半部分: %s
", port_c_lower_output ? "输出" : "输入");
    
    outb(CONTROL, control);
}
 
// 配置为模式1
void configure_mode1_group_a(bool port_a_output) {
    uint8_t control = 0x80 | 0x20;  // 位7=1, 位5=1 (模式1)
    
    if (port_a_output)
        control |= 0x10;
    
    printf("配置8255A组A为模式1:
");
    printf("  端口A: %s
", port_a_output ? "输出" : "输入");
    printf("  端口C上半部分: 用于控制/状态
");
    
    outb(CONTROL, control);
}
 
void configure_mode1_group_b(bool port_b_output) {
    uint8_t control = 0x80 | 0x04;  // 位7=1, 位2=1 (模式1)
    
    if (port_b_output)
        control |= 0x02;
    
    printf("配置8255A组B为模式1:
");
    printf("  端口B: %s
", port_b_output ? "输出" : "输入");
    printf("  端口C下半部分: 用于控制/状态
");
    
    outb(CONTROL, control);
}
 
// 配置为模式2
void configure_mode2() {
    uint8_t control = 0x80 | 0x40;  // 位7=1, 位6=1 (模式2)
    
    printf("配置8255A组A为模式2:
");
    printf("  端口A: 双向
");
    printf("  端口C上半部分: 用于控制/状态
");
    
    outb(CONTROL, control);
}
 
// 端口C位设置/复位
void set_reset_bit_port_c(int bit_position, bool set) {
    uint8_t control = 0;  // 位7=0 (位设置/复位操作)
    
    if (bit_position < 0 || bit_position > 7) {
        printf("错误: 位置必须在0-7之间
");
        return;
    }
    
    control |= bit_position;
    
    if (set)
        control |= 0x01;
    
    printf("%s端口C位%d
", set ? "设置" : "复位", bit_position);
    outb(CONTROL, control);
}
 
int main() {
    printf("8255A工作模式配置示例
");
    printf("====================

");
    
    // 模式0: 所有端口配置为输出
    configure_mode0(true, true, true, true);
    
    // 向各个端口写入数据
    outb(PORT_A, 0xAA);
    outb(PORT_B, 0x55);
    outb(PORT_C, 0x3C);
    
    printf("
");
    
    // 模式0: A输入, B输出, C上半输入, C下半输出
    configure_mode0(false, true, false, true);
    
    // 读取端口A
    inb(PORT_A);
    
    // 向端口B写入数据
    outb(PORT_B, 0xCC);
    
    // 向端口C下半部分写入数据(通过位设置/复位)
    set_reset_bit_port_c(0, true);
    set_reset_bit_port_c(1, true);
    set_reset_bit_port_c(2, false);
    set_reset_bit_port_c(3, false);
    
    printf("
");
    
    // 模式1: 组A配置为输出
    configure_mode1_group_a(true);
    
    // 向端口A写入数据
    outb(PORT_A, 0xF0);
    
    // 读取端口C状态
    inb(PORT_C);
    
    printf("
");
    
    // 模式2: 端口A配置为双向
    configure_mode2();
    
    // 双向操作:先写入后读取
    outb(PORT_A, 0x5A);
    inb(PORT_A);
    
    return 0;
}

这个示例展示了如何配置8255A的不同工作模式,以及如何在各种模式下操作端口。在实际应用中,选择合适的模式对于特定接口需求至关重要。

11.1.3 8255A的编程实践

8255A的编程主要涉及以下几个方面:

初始化配置:设置工作模式和端口方向数据输出:向输出端口写入数据数据输入:从输入端口读取数据状态监测:在模式1和模式2中,检查状态位位操作:对端口C进行位设置或复位

下面是一个完整的8255A编程实例,展示了基本I/O操作和不同模式的切换:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>  // 用于sleep函数
 
// 8255A端口地址(在实际系统中可能不同)
#define PPI_PORT_A  0x60
#define PPI_PORT_B  0x61
#define PPI_PORT_C  0x62
#define PPI_CONTROL 0x63
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%02X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    // 为演示目的,根据不同端口返回不同值
    uint8_t value = 0;
    
    switch (port) {
        case PPI_PORT_A:
            value = 0xA5;  // 模拟端口A输入
            break;
        case PPI_PORT_B:
            value = 0x5A;  // 模拟端口B输入
            break;
        case PPI_PORT_C:
            value = 0x3C;  // 模拟端口C输入
            break;
        default:
            value = 0xFF;
    }
    
    printf("读端口 0x%02X: 0x%02X
", port, value);
    return value;
}
 
// 8255A初始化(模式0)
void init_8255a_mode0(bool port_a_out, bool port_b_out, bool port_c_upper_out, bool port_c_lower_out) {
    uint8_t control = 0x80;  // 位7=1(模式设置标志)
    
    if (port_a_out)
        control |= 0x10;
    if (port_b_out)
        control |= 0x02;
    if (port_c_upper_out)
        control |= 0x08;
    if (port_c_lower_out)
        control |= 0x01;
    
    printf("
初始化8255A为模式0:
");
    printf("  端口A: %s
", port_a_out ? "输出" : "输入");
    printf("  端口B: %s
", port_b_out ? "输出" : "输入");
    printf("  端口C上半部分: %s
", port_c_upper_out ? "输出" : "输入");
    printf("  端口C下半部分: %s
", port_c_lower_out ? "输出" : "输入");
    
    outb(PPI_CONTROL, control);
}
 
// 8255A初始化(模式1,组A)
void init_8255a_mode1_group_a(bool port_a_out) {
    uint8_t control = 0x80 | 0x20;  // 位7=1, 位5=1(模式1)
    
    if (port_a_out)
        control |= 0x10;
    
    printf("
初始化8255A组A为模式1:
");
    printf("  端口A: %s
", port_a_out ? "输出" : "输入");
    printf("  端口C上半部分: 用于控制/状态
");
    
    outb(PPI_CONTROL, control);
}
 
// 8255A初始化(模式1,组B)
void init_8255a_mode1_group_b(bool port_b_out) {
    uint8_t control = 0x80 | 0x04;  // 位7=1, 位2=1(模式1)
    
    if (port_b_out)
        control |= 0x02;
    
    printf("
初始化8255A组B为模式1:
");
    printf("  端口B: %s
", port_b_out ? "输出" : "输入");
    printf("  端口C下半部分: 用于控制/状态
");
    
    outb(PPI_CONTROL, control);
}
 
// 8255A初始化(模式2)
void init_8255a_mode2(bool port_b_out, bool port_c_lower_out) {
    uint8_t control = 0x80 | 0x40;  // 位7=1, 位6=1(模式2)
    
    if (port_b_out)
        control |= 0x02;
    if (port_c_lower_out)
        control |= 0x01;
    
    printf("
初始化8255A组A为模式2:
");
    printf("  端口A: 双向
");
    printf("  端口B: %s
", port_b_out ? "输出" : "输入");
    printf("  端口C上半部分: 用于控制/状态
");
    printf("  端口C下半部分: %s
", port_c_lower_out ? "输出" : "输入");
    
    outb(PPI_CONTROL, control);
}
 
// 设置/复位端口C的单个位
void set_reset_bit_port_c(int bit, bool set) {
    if (bit < 0 || bit > 7) {
        printf("错误: 位号必须在0-7之间
");
        return;
    }
    
    uint8_t control = bit & 0x07;
    if (set)
        control |= 0x01;
    
    printf("
%s端口C的位%d
", set ? "设置" : "复位", bit);
    outb(PPI_CONTROL, control);
}
 
// 模式0示例:数字输入输出
void mode0_example() {
    printf("
===============================
");
    printf("模式0示例:数字输入输出
");
    printf("===============================
");
    
    // 配置端口A和C为输出,端口B为输入
    init_8255a_mode0(true, false, true, true);
    
    // 向端口A写入数据
    printf("
向端口A写入数据:
");
    outb(PPI_PORT_A, 0xAA);
    
    // 从端口B读取数据
    printf("
从端口B读取数据:
");
    uint8_t data = inb(PPI_PORT_B);
    
    // 通过位设置/复位操作控制端口C
    printf("
通过位设置/复位操作控制端口C:
");
    for (int i = 0; i < 8; i++) {
        set_reset_bit_port_c(i, true);  // 设置
        usleep(100000);  // 延迟100毫秒
        set_reset_bit_port_c(i, false); // 复位
        usleep(100000);  // 延迟100毫秒
    }
    
    // 直接写入端口C
    printf("
直接写入端口C:
");
    outb(PPI_PORT_C, 0x55);
}
 
// 模式1示例:选通输入输出
void mode1_example() {
    printf("
===============================
");
    printf("模式1示例:选通输入输出
");
    printf("===============================
");
    
    // 配置组A为输出模式
    init_8255a_mode1_group_a(true);
    
    // 在模式1下,需要观察状态位(端口C的特定位)
    printf("
模拟握手过程:
");
    
    // 读取端口C状态(PC7 = IBF, PC6 = ~ACK, PC5 = ~OBF, PC4 = INTR)
    printf("读取端口C状态:
");
    uint8_t status = inb(PPI_PORT_C);
    printf("状态分析:
");
    printf("  PC4 (INTR): %d
", (status & 0x10) ? 1 : 0);
    printf("  PC5 (~OBF): %d
", (status & 0x20) ? 1 : 0);
    printf("  PC6 (~ACK): %d
", (status & 0x40) ? 1 : 0);
    printf("  PC7 (IBF): %d
", (status & 0x80) ? 1 : 0);
    
    // 检查OBF标志(低有效)
    if (!(status & 0x20)) {
        printf("输出缓冲器已满,等待外设读取...
");
    } else {
        // 写入数据到端口A
        printf("向端口A写入数据:
");
        outb(PPI_PORT_A, 0xCC);
    }
    
    // 模拟外设确认接收数据
    printf("
模拟外设发送确认信号 (ACK):
");
    // 在实际系统中,这个信号由外设产生
    
    // 再次读取状态
    status = inb(PPI_PORT_C);
    printf("更新后的状态分析:
");
    printf("  PC4 (INTR): %d
", (status & 0x10) ? 1 : 0);
    printf("  PC5 (~OBF): %d
", (status & 0x20) ? 1 : 0);
    printf("  PC6 (~ACK): %d
", (status & 0x40) ? 1 : 0);
    printf("  PC7 (IBF): %d
", (status & 0x80) ? 1 : 0);
}
 
// 模式2示例:双向数据传输
void mode2_example() {
    printf("
===============================
");
    printf("模式2示例:双向数据传输
");
    printf("===============================
");
    
    // 配置模式2,端口B为输出
    init_8255a_mode2(true, true);
    
    // 在模式2下,端口A可以双向传输数据
    
    // 读取端口C状态(PC7 = IBF, PC6 = ~ACK, PC5 = ~OBF, PC4 = INTR, PC3 = INTE1, PC0 = INTE2)
    printf("
读取端口C状态:
");
    uint8_t status = inb(PPI_PORT_C);
    printf("状态分析:
");
    printf("  PC0 (INTE2): %d
", (status & 0x01) ? 1 : 0);
    printf("  PC3 (INTE1): %d
", (status & 0x08) ? 1 : 0);
    printf("  PC4 (INTR): %d
", (status & 0x10) ? 1 : 0);
    printf("  PC5 (~OBF): %d
", (status & 0x20) ? 1 : 0);
    printf("  PC6 (~ACK): %d
", (status & 0x40) ? 1 : 0);
    printf("  PC7 (IBF): %d
", (status & 0x80) ? 1 : 0);
    
    // 写入数据到端口A
    printf("
向端口A写入数据:
");
    outb(PPI_PORT_A, 0x5A);
    
    // 模拟外设确认并发送数据
    printf("
模拟外设确认并发送数据:
");
    
    // 从端口A读取数据
    printf("
从端口A读取数据:
");
    uint8_t data = inb(PPI_PORT_A);
    
    // 向端口B写入数据
    printf("
向端口B写入数据:
");
    outb(PPI_PORT_B, 0x3F);
}
 
int main() {
    printf("8255A编程实例
");
    printf("=============
");
    
    // 模式0示例
    mode0_example();
    
    // 模式1示例
    mode1_example();
    
    // 模式2示例
    mode2_example();
    
    return 0;
}

此示例详细展示了8255A的各种工作模式和编程方法,包括:

模式0:简单的数字输入输出,无握手信号模式1:带握手控制的数据传输,使用端口C作为状态和控制信号模式2:双向数据传输,端口A可以同时接收和发送数据

在实际应用中,这些编程技术可以用于连接各种并行外设,如打印机、数字I/O板卡和自定义接口电路。

11.2 8255A接口芯片的应用

11.2.1 8255A在IBM PC/XT系统中的接口应用

在IBM PC/XT及早期兼容机系统中,8255A被广泛用于实现各种外设接口。它通常用于连接键盘控制器、打印机端口和扩展I/O卡等设备。

PC/XT中的8255A配置

在标准的IBM PC/XT系统中,8255A的端口地址映射如下:

端口A:I/O地址 0x60端口B:I/O地址 0x61端口C:I/O地址 0x62控制端口:I/O地址 0x63

PC/XT中8255A的主要应用:

系统控制功能

端口B用于控制扬声器、键盘锁等端口C用于读取DIP开关和系统配置

键盘接口

端口A用于与键盘控制器通信实现键盘数据的输入和命令输出

以下是一个模拟PC/XT系统中8255A操作的示例:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
 
// PC/XT中8255A的端口地址
#define PPI_PORT_A  0x60
#define PPI_PORT_B  0x61
#define PPI_PORT_C  0x62
#define PPI_CONTROL 0x63
 
// 端口B位定义
#define TIMER_GATE      0x01  // 位0: 定时器门控制
#define SPEAKER_DATA    0x02  // 位1: 扬声器数据
#define SPEAKER_ENABLE  0x03  // 位2: 扬声器使能
#define RAM_PARITY      0x04  // 位3: RAM奇偶校验使能
#define RAM_CHECK       0x08  // 位4: RAM校验使能
#define KBD_CLOCK       0x10  // 位5: 键盘时钟
#define KBD_ENABLE      0x20  // 位6: 键盘使能
#define CASSETTE_DATA   0x40  // 位7: 磁带数据(早期PC)
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%02X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    uint8_t value;
    // 为演示目的,模拟不同端口的返回值
    switch (port) {
        case PPI_PORT_A:
            value = 0x1C;  // 模拟键盘数据
            break;
        case PPI_PORT_B:
            value = 0x01;  // 模拟端口B状态
            break;
        case PPI_PORT_C:
            value = 0xF0;  // 模拟DIP开关设置
            break;
        default:
            value = 0xFF;
    }
    printf("读端口 0x%02X: 0x%02X
", port, value);
    return value;
}
 
// 初始化PC/XT的8255A
void init_pc_ppi() {
    printf("
初始化PC/XT的8255A...
");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=00: 组A工作在模式0
    // 位4=0: 端口A为输入(用于键盘数据)
    // 位3=1: 端口C上半部分为输出
    // 位2=0: 组B工作在模式0
    // 位1=1: 端口B为输出(用于系统控制)
    // 位0=1: 端口C下半部分为输出
    uint8_t control = 0x82 | 0x08 | 0x01;
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为:
");
    printf("  端口A: 输入(键盘数据)
");
    printf("  端口B: 输出(系统控制)
");
    printf("  端口C上半部分: 输出
");
    printf("  端口C下半部分: 输出
");
}
 
// 读取PC/XT的配置DIP开关(通过端口C)
void read_dip_switches() {
    printf("
读取系统配置DIP开关...
");
    
    uint8_t switches = inb(PPI_PORT_C);
    
    printf("DIP开关设置 (0x%02X):
", switches);
    printf("  显示器类型: %s
", (switches & 0x30) == 0x30 ? "单色" :
                               (switches & 0x30) == 0x20 ? "40列彩色" :
                               (switches & 0x30) == 0x10 ? "80列彩色" : "保留");
    
    printf("  内存大小: ");
    switch ((switches & 0xC0) >> 6) {
        case 0: printf("16KB
"); break;
        case 1: printf("32KB
"); break;
        case 2: printf("48KB
"); break;
        case 3: printf("64KB
"); break;
    }
    
    printf("  磁盘驱动器数量: %d
", ((switches & 0x01) ? 1 : 0) + ((switches & 0x02) ? 1 : 0));
    printf("  协处理器: %s
", (switches & 0x02) ? "已安装" : "未安装");
    printf("  启动从磁盘: %s
", (switches & 0x01) ? "是" : "否");
}
 
// 控制PC/XT的扬声器
void control_speaker(bool enable, bool data) {
    printf("
控制PC/XT扬声器...
");
    
    // 首先读取当前端口B值
    uint8_t port_b = inb(PPI_PORT_B);
    
    // 修改扬声器控制位
    if (enable) {
        port_b |= SPEAKER_ENABLE;
        printf("扬声器使能
");
    } else {
        port_b &= ~SPEAKER_ENABLE;
        printf("扬声器禁用
");
    }
    
    if (data) {
        port_b |= SPEAKER_DATA;
        printf("扬声器数据位设置
");
    } else {
        port_b &= ~SPEAKER_DATA;
        printf("扬声器数据位清除
");
    }
    
    // 写回端口B
    outb(PPI_PORT_B, port_b);
}
 
// 读取键盘数据
uint8_t read_keyboard() {
    printf("
读取键盘数据...
");
    
    // 在PC/XT中,键盘数据通过端口A读取
    uint8_t key_data = inb(PPI_PORT_A);
    
    printf("键盘扫描码: 0x%02X
", key_data);
    
    // 对于演示目的,模拟一些常见按键
    if (key_data == 0x1C) {
        printf("按键解析: Enter键
");
    } else if (key_data == 0x01) {
        printf("按键解析: Esc键
");
    } else if (key_data == 0x39) {
        printf("按键解析: 空格键
");
    }
    
    return key_data;
}
 
// 控制系统定时器门
void control_timer_gate(bool enable) {
    printf("
控制系统定时器门...
");
    
    // 读取当前端口B值
    uint8_t port_b = inb(PPI_PORT_B);
    
    // 修改定时器门控制位
    if (enable) {
        port_b |= TIMER_GATE;
        printf("定时器门已使能
");
    } else {
        port_b &= ~TIMER_GATE;
        printf("定时器门已禁用
");
    }
    
    // 写回端口B
    outb(PPI_PORT_B, port_b);
}
 
int main() {
    printf("IBM PC/XT中的8255A应用示例
");
    printf("========================
");
    
    // 初始化PPI
    init_pc_ppi();
    
    // 读取配置DIP开关
    read_dip_switches();
    
    // 控制扬声器
    control_speaker(true, true);   // 打开扬声器
    usleep(500000);                // 等待500ms
    control_speaker(false, false); // 关闭扬声器
    
    // 读取键盘
    read_keyboard();
    
    // 控制定时器门
    control_timer_gate(true);
    usleep(200000);
    control_timer_gate(false);
    
    return 0;
}

这个示例展示了8255A在PC/XT系统中的典型用途,包括系统配置读取、扬声器控制和键盘接口。虽然现代PC系统已经不再直接使用8255A,但了解这种经典接口技术有助于理解计算机系统的发展历史。

11.2.2 使用8255A模式0实现打印机接口

并行打印机接口是8255A的一个经典应用场景。在模式0下,8255A可以轻松实现Centronics标准的并行打印机接口。

并行打印机接口信号

数据线(DATA0-DATA7):8位并行数据控制信号
STROBE:数据有效信号,低电平有效AUTO FEED:自动换行,低电平有效INIT:打印机初始化,低电平有效SELECT IN:选择打印机,低电平有效
状态信号
ACK:确认信号,低电平有效BUSY:打印机忙,高电平有效PAPER END:缺纸信号,高电平有效SELECT:打印机在线,高电平有效ERROR:错误信号,低电平有效

8255A配置方案

端口A:连接数据线(DATA0-DATA7),配置为输出端口C上半部分:连接状态信号(BUSY, ACK, PAPER END, SELECT),配置为输入端口C下半部分:连接控制信号(STROBE, AUTO FEED, INIT, SELECT IN),配置为输出

下面是使用8255A模式0实现打印机接口的代码示例:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
 
// 8255A端口地址
#define PPI_PORT_A  0x378       // 打印机数据端口
#define PPI_PORT_B  0x379       // 打印机状态端口
#define PPI_PORT_C  0x37A       // 打印机控制端口
#define PPI_CONTROL 0x37B       // 控制寄存器
 
// 打印机状态位(端口B)
#define PRINTER_ACK      0x08   // 位3: ~ACK
#define PRINTER_BUSY     0x80   // 位7: BUSY
#define PRINTER_PAPER_END 0x20  // 位5: PAPER END
#define PRINTER_SELECT   0x10   // 位4: SELECT
#define PRINTER_ERROR    0x40   // 位6: ~ERROR
 
// 打印机控制位(端口C)
#define PRINTER_STROBE    0x01  // 位0: ~STROBE
#define PRINTER_AUTO_FEED 0x02  // 位1: ~AUTO FEED
#define PRINTER_INIT      0x04  // 位2: ~INIT
#define PRINTER_SELECT_IN 0x08  // 位3: ~SELECT IN
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%03X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    uint8_t value = 0;
    // 为演示目的,模拟打印机状态
    if (port == PPI_PORT_B) {
        value = 0x10 | 0x08; // SELECT=1, ERROR=1, ~ACK=1, BUSY=0
    }
    printf("读端口 0x%03X: 0x%02X
", port, value);
    return value;
}
 
// 初始化8255A作为打印机接口
void init_printer_interface() {
    printf("
初始化8255A作为打印机接口...
");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=00: 组A工作在模式0
    // 位4=1: 端口A为输出(数据线)
    // 位3=0: 端口C上半部分为输入(状态信号)
    // 位2=0: 组B工作在模式0
    // 位1=0: 端口B为输入(未使用)
    // 位0=1: 端口C下半部分为输出(控制信号)
    uint8_t control = 0x80 | 0x10 | 0x01;
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为打印机接口:
");
    printf("  端口A: 输出(打印机数据)
");
    printf("  端口B: 输入(未使用)
");
    printf("  端口C上半部分: 输入(打印机状态)
");
    printf("  端口C下半部分: 输出(打印机控制)
");
    
    // 初始化控制信号
    // 设置INIT=0(复位打印机)
    outb(PPI_PORT_C, 0x00);
    usleep(100000); // 等待100ms
    
    // 设置INIT=1, SELECT IN=0, AUTO FEED=1, STROBE=1
    outb(PPI_PORT_C, PRINTER_INIT | PRINTER_AUTO_FEED | PRINTER_STROBE);
}
 
// 检查打印机状态
bool check_printer_status() {
    printf("
检查打印机状态...
");
    
    uint8_t status = inb(PPI_PORT_B);
    
    printf("打印机状态:
");
    printf("  BUSY: %s
", (status & PRINTER_BUSY) ? "是" : "否");
    printf("  ACK: %s
", (status & PRINTER_ACK) ? "否" : "是");
    printf("  PAPER END: %s
", (status & PRINTER_PAPER_END) ? "是" : "否");
    printf("  SELECT: %s
", (status & PRINTER_SELECT) ? "是" : "否");
    printf("  ERROR: %s
", (status & PRINTER_ERROR) ? "否" : "是");
    
    // 检查打印机是否准备好接收数据
    bool ready = (status & PRINTER_SELECT) && !(status & PRINTER_BUSY) && !(status & PRINTER_PAPER_END);
    printf("打印机状态: %s
", ready ? "就绪" : "未就绪");
    
    return ready;
}
 
// 向打印机发送一个字节
bool send_byte_to_printer(uint8_t data) {
    printf("
向打印机发送字节 0x%02X ('%c')...
", data, (data >= 32 && data < 127) ? data : ' ');
    
    // 检查打印机是否就绪
    if (!check_printer_status()) {
        printf("打印机未就绪,无法发送数据
");
        return false;
    }
    
    // 获取当前控制寄存器值
    uint8_t control = PRINTER_INIT | PRINTER_AUTO_FEED | PRINTER_STROBE;
    
    // 1. 将数据放到端口A
    outb(PPI_PORT_A, data);
    
    // 2. 生成STROBE信号(低脉冲)
    outb(PPI_PORT_C, control & ~PRINTER_STROBE); // STROBE = 0
    usleep(1000); // 等待1ms
    outb(PPI_PORT_C, control); // STROBE = 1
    
    // 3. 等待ACK信号
    printf("等待打印机ACK信号...
");
    
    // 在实际应用中,应该轮询ACK信号,这里简化处理
    usleep(5000); // 假设5ms后收到ACK
    
    printf("打印机已确认接收数据
");
    return true;
}
 
// 向打印机发送一串字符
bool print_string(const char *str) {
    printf("
打印字符串: "%s"
", str);
    
    size_t len = strlen(str);
    for (size_t i = 0; i < len; i++) {
        if (!send_byte_to_printer((uint8_t)str[i])) {
            printf("打印失败于字符 '%c'
", str[i]);
            return false;
        }
    }
    
    // 发送换行符
    send_byte_to_printer('
');
    send_byte_to_printer('
');
    
    printf("字符串打印完成
");
    return true;
}
 
// 重置打印机
void reset_printer() {
    printf("
重置打印机...
");
    
    // 生成INIT低脉冲
    uint8_t control = PRINTER_AUTO_FEED | PRINTER_STROBE; // INIT = 0
    outb(PPI_PORT_C, control);
    usleep(100000); // 等待100ms
    
    control |= PRINTER_INIT; // INIT = 1
    outb(PPI_PORT_C, control);
    
    printf("打印机已重置
");
}
 
int main() {
    printf("8255A模式0打印机接口示例
");
    printf("========================
");
    
    // 初始化打印机接口
    init_printer_interface();
    
    // 检查打印机状态
    check_printer_status();
    
    // 打印测试字符串
    print_string("Hello, this is a test from 8255A!");
    print_string("Testing parallel printer interface");
    
    // 重置打印机
    reset_printer();
    
    return 0;
}

这个示例展示了如何使用8255A的模式0实现标准Centronics并行打印机接口。关键步骤包括:

初始化8255A,将端口A配置为数据输出,端口C上半部分配置为状态输入,端口C下半部分配置为控制输出检查打印机状态,确保它已就绪发送数据到端口A,并通过STROBE信号通知打印机等待打印机的ACK信号,表示数据已被接收

虽然现代系统中并行打印机接口已被USB等接口取代,但这种实现方法仍然展示了8255A在并行数据传输中的应用原理。

11.2.3 使用8255A模式1实现高效打印机接口

8255A的模式1提供了带握手功能的数据传输能力,使其更适合实现高效的打印机接口。在这种模式下,8255A会自动管理STROBE和ACK等握手信号,简化了编程复杂度。

模式1下的打印机接口配置

端口A:配置为输出,连接数据线端口C上半部分:自动管理握手信号(PC7-PC4)
PC6: ~ACK(输入)PC5: ~OBF(输出)- 输出缓冲满标志PC4: INTR(输出)- 中断请求
端口C下半部分:连接其他控制信号(AUTO FEED, INIT, SELECT IN)

下面是使用8255A模式1实现高效打印机接口的代码示例:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
 
// 8255A端口地址
#define PPI_PORT_A  0x378       // 打印机数据端口
#define PPI_PORT_B  0x379       // 未使用
#define PPI_PORT_C  0x37A       // 打印机控制/状态端口
#define PPI_CONTROL 0x37B       // 控制寄存器
 
// 打印机状态位(端口C)
#define PRINTER_ACK      0x40   // 位6: ~ACK
#define PRINTER_OBF      0x20   // 位5: ~OBF (输出缓冲满)
#define PRINTER_INTR     0x10   // 位4: INTR (中断请求)
#define PRINTER_BUSY     0x80   // 外部BUSY信号
 
// 打印机控制位(端口C)
#define PRINTER_AUTO_FEED 0x02  // 位1: ~AUTO FEED
#define PRINTER_INIT      0x04  // 位2: ~INIT
#define PRINTER_SELECT_IN 0x08  // 位3: ~SELECT IN
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%03X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    uint8_t value = 0;
    // 为演示目的,模拟打印机状态
    if (port == PPI_PORT_C) {
        static int counter = 0;
        // 每调用3次,模拟一次ACK信号
        if (++counter % 3 == 0) {
            value = 0x00; // ~ACK = 0 (有效)
        } else {
            value = PRINTER_ACK; // ~ACK = 1 (无效)
        }
        
        // 随机添加BUSY状态
        if (counter % 5 == 0) {
            value |= PRINTER_BUSY;
        }
    }
    printf("读端口 0x%03X: 0x%02X
", port, value);
    return value;
}
 
// 初始化8255A作为模式1打印机接口
void init_printer_interface_mode1() {
    printf("
初始化8255A作为模式1打印机接口...
");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=01: 组A工作在模式1
    // 位4=1: 端口A为输出(数据线)
    // 位3=X: 在模式1下,端口C上半部分自动配置为控制/状态
    // 位2=0: 组B工作在模式0
    // 位1=0: 端口B为输入(未使用)
    // 位0=1: 端口C下半部分为输出(其他控制信号)
    uint8_t control = 0x80 | 0x20 | 0x10 | 0x01;
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为模式1打印机接口:
");
    printf("  端口A: 输出(打印机数据)
");
    printf("  端口B: 输入(未使用)
");
    printf("  端口C上半部分: 控制/状态(自动管理握手信号)
");
    printf("  端口C下半部分: 输出(其他打印机控制信号)
");
    
    // 初始化控制信号
    // 在模式1下,我们只需控制PC0-PC3,其他位由8255A自动管理
    // 设置INIT=0(复位打印机)
    outb(PPI_PORT_C, 0x00);
    usleep(100000); // 等待100ms
    
    // 设置INIT=1, SELECT IN=0, AUTO FEED=1
    outb(PPI_PORT_C, PRINTER_INIT | PRINTER_AUTO_FEED);
}
 
// 检查打印机状态
bool check_printer_status_mode1() {
    printf("
检查打印机状态...
");
    
    uint8_t status = inb(PPI_PORT_C);
    
    printf("打印机状态:
");
    printf("  BUSY: %s
", (status & PRINTER_BUSY) ? "是" : "否");
    printf("  ACK: %s
", (status & PRINTER_ACK) ? "否" : "是");
    printf("  OBF: %s
", (status & PRINTER_OBF) ? "否" : "是");
    printf("  INTR: %s
", (status & PRINTER_INTR) ? "是" : "否");
    
    // 在模式1下,主要检查BUSY和OBF信号
    bool ready = !(status & PRINTER_BUSY) && !(status & PRINTER_OBF);
    printf("打印机状态: %s
", ready ? "就绪" : "未就绪");
    
    return ready;
}
 
// 向打印机发送一个字节(模式1)
bool send_byte_to_printer_mode1(uint8_t data) {
    printf("
向打印机发送字节 0x%02X ('%c')...
", data, (data >= 32 && data < 127) ? data : ' ');
    
    // 检查打印机是否就绪
    if (!check_printer_status_mode1()) {
        printf("打印机未就绪,无法发送数据
");
        return false;
    }
    
    // 在模式1下,只需将数据写入端口A
    // STROBE信号会由8255A自动生成
    outb(PPI_PORT_A, data);
    
    // 在模式1下,8255A会自动设置OBF标志,并生成STROBE信号
    printf("数据已发送,等待打印机确认...
");
    
    // 等待ACK信号(通过轮询INTR标志)
    int timeout = 10; // 最多等待10次
    bool ack_received = false;
    
    while (timeout-- > 0) {
        uint8_t status = inb(PPI_PORT_C);
        if (status & PRINTER_INTR) {
            printf("收到打印机ACK信号
");
            ack_received = true;
            break;
        }
        usleep(1000); // 等待1ms
    }
    
    if (!ack_received) {
        printf("等待ACK超时
");
        return false;
    }
    
    return true;
}
 
// 向打印机发送一串字符(模式1)
bool print_string_mode1(const char *str) {
    printf("
打印字符串: "%s"
", str);
    
    size_t len = strlen(str);
    for (size_t i = 0; i < len; i++) {
        if (!send_byte_to_printer_mode1((uint8_t)str[i])) {
            printf("打印失败于字符 '%c'
", str[i]);
            return false;
        }
    }
    
    // 发送换行符
    send_byte_to_printer_mode1('
');
    send_byte_to_printer_mode1('
');
    
    printf("字符串打印完成
");
    return true;
}
 
// 重置打印机
void reset_printer_mode1() {
    printf("
重置打印机...
");
    
    // 读取当前控制寄存器值(保留上半部分的状态位)
    uint8_t current = inb(PPI_PORT_C) & 0xF0;
    
    // 生成INIT低脉冲
    uint8_t control = current | PRINTER_AUTO_FEED; // INIT = 0
    outb(PPI_PORT_C, control);
    usleep(100000); // 等待100ms
    
    control |= PRINTER_INIT; // INIT = 1
    outb(PPI_PORT_C, control);
    
    printf("打印机已重置
");
}
 
int main() {
    printf("8255A模式1打印机接口示例
");
    printf("========================
");
    
    // 初始化打印机接口(模式1)
    init_printer_interface_mode1();
    
    // 检查打印机状态
    check_printer_status_mode1();
    
    // 打印测试字符串
    print_string_mode1("Hello from 8255A Mode 1!");
    print_string_mode1("Efficient printer interface with handshaking");
    
    // 重置打印机
    reset_printer_mode1();
    
    return 0;
}

模式1相比模式0的主要优势在于:

自动握手控制:8255A自动管理STROBE和ACK信号,减轻了CPU的负担中断支持:可以通过INTR信号生成中断,使系统更高效地响应打印机的状态变化缓冲状态监控:OBF标志自动指示输出缓冲区状态,简化了状态检查

这种实现方式特别适合需要高效数据传输的打印机应用,尤其是在数据传输速度较快或系统负载较重的情况下。

11.2.4 实现计算机间的并行通信接口

8255A的另一个重要应用是实现计算机之间的高速并行数据传输。特别是在模式2下,8255A提供的双向通信能力使其成为实现这类接口的理想选择。

计算机间并行通信的基本原理

两台计算机分别通过8255A连接端口A用于双向数据传输端口C的控制/状态位用于同步和握手端口B可用于辅助控制或额外数据传输

以下是使用8255A模式2实现计算机间并行通信的示例代码:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
 
// 8255A端口地址
#define PPI_PORT_A  0x300       // 数据传输端口
#define PPI_PORT_B  0x301       // 辅助数据端口
#define PPI_PORT_C  0x302       // 控制/状态端口
#define PPI_CONTROL 0x303       // 控制寄存器
 
// 状态位定义(端口C)
#define PC7_IBF      0x80       // 位7: 输入缓冲满
#define PC6_ACK      0x40       // 位6: ~ACK
#define PC5_OBF      0x20       // 位5: ~OBF(输出缓冲满)
#define PC4_INTR     0x10       // 位4: INTR(中断请求)
#define PC3_INTE1    0x08       // 位3: INTE1(输入中断使能)
#define PC0_INTE2    0x01       // 位0: INTE2(输出中断使能)
 
// 通信角色定义
typedef enum {
    COMM_ROLE_MASTER,
    COMM_ROLE_SLAVE
} CommRole;
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%03X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    uint8_t value = 0;
    static int counter = 0;
    
    // 为演示目的,模拟不同端口的返回值
    switch (port) {
        case PPI_PORT_A:
            // 模拟接收到的数据,每次调用返回不同值
            counter++;
            value = 0xA0 + (counter % 26);
            break;
        case PPI_PORT_C:
            // 模拟状态位变化
            if (counter % 3 == 0) {
                value = PC7_IBF;  // 输入缓冲满
            } else if (counter % 3 == 1) {
                value = PC5_OBF;  // 输出缓冲满
            } else {
                value = PC4_INTR;  // 中断请求
            }
            break;
        default:
            value = 0xFF;
    }
    
    printf("读端口 0x%03X: 0x%02X
", port, value);
    return value;
}
 
// 初始化8255A作为并行通信接口
void init_parallel_comm(CommRole role) {
    printf("
初始化8255A作为并行通信接口 (%s)...
", 
           role == COMM_ROLE_MASTER ? "主机" : "从机");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=11: 组A工作在模式2(双向)
    // 位4=X: 在模式2下,端口A自动配置为双向
    // 位3=1: 端口C上半部分为输入(在模式2下由8255A自动管理)
    // 位2=0: 组B工作在模式0
    // 位1=1: 端口B为输出(辅助数据)
    // 位0=1: 端口C下半部分为输出(辅助控制)
    uint8_t control = 0x80 | 0x60 | 0x08 | 0x02 | 0x01;
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为并行通信接口:
");
    printf("  端口A: 双向(数据传输)
");
    printf("  端口B: 输出(辅助数据)
");
    printf("  端口C上半部分: 控制/状态(自动管理)
");
    printf("  端口C下半部分: 输出(辅助控制)
");
    
    // 设置中断使能位
    // 使能输入中断(PC3)和输出中断(PC0)
    uint8_t current = inb(PPI_PORT_C);
    outb(PPI_PORT_C, current | PC3_INTE1 | PC0_INTE2);
}
 
// 检查通信状态
bool check_comm_status() {
    printf("
检查通信状态...
");
    
    uint8_t status = inb(PPI_PORT_C);
    
    printf("通信状态:
");
    printf("  IBF: %s
", (status & PC7_IBF) ? "满" : "空");
    printf("  ACK: %s
", (status & PC6_ACK) ? "否" : "是");
    printf("  OBF: %s
", (status & PC5_OBF) ? "满" : "空");
    printf("  INTR: %s
", (status & PC4_INTR) ? "有" : "无");
    printf("  INTE1: %s
", (status & PC3_INTE1) ? "使能" : "禁止");
    printf("  INTE2: %s
", (status & PC0_INTE2) ? "使能" : "禁止");
    
    return true;
}
 
// 发送数据
bool send_data(uint8_t data) {
    printf("
发送数据: 0x%02X ('%c')...
", data, (data >= 32 && data < 127) ? data : ' ');
    
    // 检查输出缓冲区是否已满(OBF为0表示缓冲区空)
    uint8_t status = inb(PPI_PORT_C);
    if (!(status & PC5_OBF)) {
        outb(PPI_PORT_A, data);
        printf("数据已写入端口A
");
        
        // 在模式2下,8255A会自动设置OBF标志
        printf("等待接收确认...
");
        
        // 模拟等待ACK
        usleep(10000);
        
        // 检查ACK
        status = inb(PPI_PORT_C);
        if (!(status & PC6_ACK)) {
            printf("收到接收确认
");
            return true;
        } else {
            printf("未收到接收确认
");
            return false;
        }
    } else {
        printf("输出缓冲区已满,无法发送数据
");
        return false;
    }
}
 
// 接收数据
bool receive_data(uint8_t *data) {
    printf("
尝试接收数据...
");
    
    // 检查输入缓冲区是否有数据(IBF为1表示缓冲区满)
    uint8_t status = inb(PPI_PORT_C);
    if (status & PC7_IBF) {
        // 读取数据
        *data = inb(PPI_PORT_A);
        printf("收到数据: 0x%02X ('%c')
", *data, (*data >= 32 && *data < 127) ? *data : ' ');
        
        // 模式2下,读取操作会自动产生ACK信号
        
        return true;
    } else {
        printf("输入缓冲区为空,没有数据可接收
");
        return false;
    }
}
 
// 发送字符串
bool send_string(const char *str) {
    printf("
发送字符串: "%s"
", str);
    
    size_t len = strlen(str);
    for (size_t i = 0; i < len; i++) {
        if (!send_data((uint8_t)str[i])) {
            printf("发送失败于字符 '%c'
", str[i]);
            return false;
        }
        usleep(5000); // 短暂延迟,模拟实际传输延迟
    }
    
    // 发送结束标记
    send_data(0);
    
    printf("字符串发送完成
");
    return true;
}
 
// 接收字符串
bool receive_string(char *buffer, size_t max_len) {
    printf("
接收字符串...
");
    
    size_t idx = 0;
    uint8_t data;
    
    while (idx < max_len - 1) {
        if (receive_data(&data)) {
            if (data == 0) { // 结束标记
                break;
            }
            buffer[idx++] = (char)data;
        } else {
            usleep(5000); // 等待5ms再次尝试
        }
    }
    
    buffer[idx] = ''; // 添加字符串结束符
    
    printf("接收到字符串: "%s"
", buffer);
    return true;
}
 
// 主机模式演示
void demo_master_mode() {
    printf("
=========================
");
    printf("并行通信主机模式演示
");
    printf("=========================
");
    
    // 初始化为主机
    init_parallel_comm(COMM_ROLE_MASTER);
    
    // 检查通信状态
    check_comm_status();
    
    // 发送命令
    send_string("GET STATUS");
    
    // 等待响应
    char response[256];
    receive_string(response, sizeof(response));
    
    // 发送数据
    send_string("DATA: This is a test message from master");
    
    // 接收确认
    receive_string(response, sizeof(response));
    
    // 发送结束命令
    send_string("END");
}
 
// 从机模式演示
void demo_slave_mode() {
    printf("
=========================
");
    printf("并行通信从机模式演示
");
    printf("=========================
");
    
    // 初始化为从机
    init_parallel_comm(COMM_ROLE_SLAVE);
    
    // 检查通信状态
    check_comm_status();
    
    // 等待命令
    char command[256];
    receive_string(command, sizeof(command));
    
    // 处理命令
    if (strcmp(command, "GET STATUS") == 0) {
        // 发送状态信息
        send_string("STATUS: Ready, all systems normal");
    }
    
    // 等待更多数据
    receive_string(command, sizeof(command));
    
    // 发送确认
    if (strncmp(command, "DATA:", 5) == 0) {
        send_string("ACK: Data received successfully");
    }
    
    // 等待结束命令
    receive_string(command, sizeof(command));
}
 
int main() {
    printf("8255A并行通信接口示例
");
    printf("=====================
");
    
    // 模拟主机模式
    demo_master_mode();
    
    printf("
----------------------------
");
    
    // 模拟从机模式
    demo_slave_mode();
    
    return 0;
}

这个示例演示了如何使用8255A的模式2来实现计算机间的双向并行通信。关键特性包括:

双向数据传输:端口A用于双向数据交换自动握手控制:8255A自动管理IBF、OBF和ACK等握手信号中断支持:可以通过INTR信号生成中断,支持中断驱动的通信辅助数据/控制:端口B和端口C下半部分可用于传输辅助信息或控制信号

这种并行通信接口特别适用于以下场景:

两台计算机间的高速数据交换计算机与专用设备间的通信数据采集系统与主机间的接口老式网络连接(如LapLink等并行电缆连接)

尽管现代系统多采用USB、以太网等串行接口,但在某些特定应用中,基于8255A的并行通信接口仍然具有简单、可靠、高速的优势。

11.3 键盘接口技术与实现

11.3.1 简易键盘矩阵原理与接口设计

键盘是计算机系统最基本的输入设备。简易键盘通常采用矩阵扫描技术,可以通过8255A实现与计算机的接口。

键盘矩阵扫描原理

键盘按键排列成行和列的矩阵行线连接到8255A的输出端口列线连接到8255A的输入端口通过逐行扫描,检测每一列的状态根据行号和列号确定按键位置

以下是使用8255A实现4×4键盘矩阵接口的示例:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
 
// 8255A端口地址
#define PPI_PORT_A  0x80       // 行输出端口
#define PPI_PORT_B  0x81       // 列输入端口
#define PPI_PORT_C  0x82       // 辅助端口
#define PPI_CONTROL 0x83       // 控制寄存器
 
// 键盘矩阵行数和列数
#define MATRIX_ROWS 4
#define MATRIX_COLS 4
 
// 键盘矩阵定义(4×4按键阵列)
const char key_matrix[MATRIX_ROWS][MATRIX_COLS] = {
    { '1', '2', '3', 'A' },
    { '4', '5', '6', 'B' },
    { '7', '8', '9', 'C' },
    { '*', '0', '#', 'D' }
};
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%02X: 0x%02X
", port, value);
}
 
// 模拟列输入,根据输出的行和虚拟按键状态返回列状态
uint8_t simulate_keyboard_input(uint8_t row_output) {
    static int pressed_key_row = 2;    // 模拟按下第3行
    static int pressed_key_col = 1;    // 模拟按下第2列(即数字"8")
    
    uint8_t column_input = 0xFF;       // 默认所有列为高电平(未按下)
    
    // 检查当前扫描的行是否包含按下的按键
    if ((row_output & (1 << pressed_key_row)) == 0) {
        // 行被激活(低电平),将对应列置为低电平
        column_input &= ~(1 << pressed_key_col);
    }
    
    return column_input;
}
 
uint8_t inb(uint16_t port) {
    uint8_t value = 0xFF;  // 默认返回全高电平
    
    if (port == PPI_PORT_B) {
        // 从全局变量获取当前行输出值
        static uint8_t last_row_output = 0;
        value = simulate_keyboard_input(last_row_output);
    }
    
    printf("读端口 0x%02X: 0x%02X
", port, value);
    return value;
}
 
// 保存上一次读取的行值,用于模拟
void update_last_row_output(uint8_t value) {
    static uint8_t last_row_output = 0;
    last_row_output = value;
}
 
// 初始化8255A作为键盘接口
void init_keyboard_interface() {
    printf("
初始化8255A作为键盘接口...
");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=00: 组A工作在模式0
    // 位4=1: 端口A为输出(行扫描)
    // 位3=0: 端口C上半部分为输入
    // 位2=0: 组B工作在模式0
    // 位1=0: 端口B为输入(列检测)
    // 位0=0: 端口C下半部分为输入
    uint8_t control = 0x80 | 0x10;
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为键盘接口:
");
    printf("  端口A: 输出(行扫描信号)
");
    printf("  端口B: 输入(列状态检测)
");
    printf("  端口C: 输入(辅助功能)
");
}
 
// 扫描键盘矩阵
char scan_keyboard() {
    printf("
开始扫描键盘矩阵...
");
    
    char pressed_key = 0;  // 默认没有按键按下
    
    // 逐行扫描
    for (int row = 0; row < MATRIX_ROWS; row++) {
        // 生成行扫描信号(低电平有效)
        // 当前行为低电平,其余行为高电平
        uint8_t row_scan = ~(1 << row);
        
        printf("扫描行 %d,输出信号: 0x%02X
", row, row_scan);
        
        // 输出到端口A
        outb(PPI_PORT_A, row_scan);
        update_last_row_output(row_scan);
        
        // 短暂延迟,等待信号稳定
        usleep(5000);
        
        // 读取列状态
        uint8_t col_status = inb(PPI_PORT_B);
        
        // 检查每一列
        for (int col = 0; col < MATRIX_COLS; col++) {
            // 检查该列是否有按键按下(低电平)
            if (!(col_status & (1 << col))) {
                // 找到按下的按键
                pressed_key = key_matrix[row][col];
                printf("检测到按键按下: 行=%d, 列=%d, 按键='%c'
", 
                       row, col, pressed_key);
            }
        }
    }
    
    // 所有行恢复高电平
    outb(PPI_PORT_A, 0xFF);
    update_last_row_output(0xFF);
    
    return pressed_key;
}
 
// 简单的键盘去抖动
char debounce_and_scan() {
    printf("
执行带去抖的键盘扫描...
");
    
    char key1 = scan_keyboard();
    
    if (key1 != 0) {
        // 延时20ms
        usleep(20000);
        
        // 再次扫描
        char key2 = scan_keyboard();
        
        // 如果两次扫描结果一致,则确认为有效按键
        if (key1 == key2) {
            printf("按键确认: '%c'
", key1);
            return key1;
        }
    }
    
    return 0;  // 没有稳定的按键按下
}
 
// 键盘读取循环
void keyboard_read_loop() {
    printf("
启动键盘读取循环...
");
    printf("(这是一个模拟,会检测到预设的按键)
");
    
    int iterations = 3;  // 循环次数(实际应用中通常是无限循环)
    
    for (int i = 0; i < iterations; i++) {
        printf("
扫描周期 %d:
", i + 1);
        
        char key = debounce_and_scan();
        
        if (key != 0) {
            printf("处理按键: '%c'
", key);
            // 在这里处理按键,如显示、存储或执行命令
        } else {
            printf("没有检测到按键
");
        }
        
        // 延时100ms
        usleep(100000);
    }
}
 
// 键盘自检
bool keyboard_self_test() {
    printf("
执行键盘自检...
");
    
    // 测试行输出
    printf("测试行输出线路...
");
    for (int row = 0; row < MATRIX_ROWS; row++) {
        uint8_t pattern = ~(1 << row);
        outb(PPI_PORT_A, pattern);
        update_last_row_output(pattern);
        usleep(10000);  // 延时10ms
    }
    
    // 恢复所有行为高电平
    outb(PPI_PORT_A, 0xFF);
    update_last_row_output(0xFF);
    
    // 读取列状态(检查是否有卡住的按键)
    printf("检查卡住的按键...
");
    uint8_t col_status = inb(PPI_PORT_B);
    
    bool all_clear = (col_status == 0xFF);
    printf("自检结果: %s
", all_clear ? "通过" : "失败");
    
    return all_clear;
}
 
// 显示键盘映射
void display_keyboard_mapping() {
    printf("
键盘矩阵映射:
");
    printf("+-----------------------+
");
    
    for (int row = 0; row < MATRIX_ROWS; row++) {
        printf("| ");
        for (int col = 0; col < MATRIX_COLS; col++) {
            printf(" %c |", key_matrix[row][col]);
        }
        printf("
+-----------------------+
");
    }
}
 
int main() {
    printf("8255A键盘矩阵接口示例
");
    printf("====================
");
    
    // 显示键盘映射
    display_keyboard_mapping();
    
    // 初始化键盘接口
    init_keyboard_interface();
    
    // 执行键盘自检
    if (keyboard_self_test()) {
        // 启动键盘读取循环
        keyboard_read_loop();
    } else {
        printf("键盘自检失败,请检查硬件连接
");
    }
    
    return 0;
}

这个示例展示了如何使用8255A实现一个4×4键盘矩阵接口,主要包括以下步骤:

初始化8255A,将端口A配置为输出(行扫描),端口B配置为输入(列检测)通过逐行置低电平,扫描每一行对于当前扫描的行,读取列状态,检查是否有按键按下根据行号和列号查表确定按下的按键实现简单的去抖动处理,确保按键信号稳定

这种键盘扫描技术具有以下特点:

硬件简单,只需要少量的外部元件可以处理同时按下的多个按键(取决于键盘矩阵的布局)扫描速度快,适合实时应用按键数量可以通过增加矩阵大小进行扩展

尽管简易键盘矩阵主要用于小型设备,但这种接口技术是理解更复杂键盘系统的基础。

11.3.2 PC键盘控制器接口原理

PC系统的键盘接口比简单的键盘矩阵更为复杂,它使用专门的键盘控制器芯片(如Intel 8042)与键盘通信。在早期PC系统中,8255A被用来与键盘控制器进行接口。

PC键盘控制器接口结构

键盘本身包含一个微控制器,负责扫描按键并生成扫描码键盘控制器(8042)与键盘之间通过串行协议通信CPU通过I/O端口与键盘控制器通信8255A的端口A用于数据传输,端口B用于控制和状态

以下是一个模拟PC键盘控制器接口的示例:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
 
// 8255A端口地址(在PC/XT中的映射)
#define PPI_PORT_A  0x60       // 键盘数据端口
#define PPI_PORT_B  0x61       // 系统控制端口
#define PPI_PORT_C  0x62       // 系统配置端口
#define PPI_CONTROL 0x63       // 控制寄存器
 
// 端口B位定义
#define KB_ENABLE   0x80       // 位7: 键盘使能标志
#define KB_CLOCK    0x40       // 位6: 键盘时钟
#define RAM_PARITY  0x20       // 位5: RAM奇偶校验
 
// 键盘控制器命令
#define KC_READ_MODE     0x20   // 读取键盘控制器模式
#define KC_WRITE_MODE    0x60   // 写入键盘控制器模式
#define KC_SELF_TEST     0xAA   // 控制器自检
#define KC_KB_TEST       0xAB   // 键盘接口测试
#define KC_DISABLE_KB    0xAD   // 禁用键盘接口
#define KC_ENABLE_KB     0xAE   // 启用键盘接口
 
// 键盘命令
#define KB_RESET         0xFF   // 复位键盘
#define KB_SET_LEDS      0xED   // 设置LED状态
#define KB_ECHO          0xEE   // 回显命令
#define KB_GET_ID        0xF2   // 获取键盘ID
 
// 键盘响应码
#define KB_ACK           0xFA   // 确认命令
#define KB_RESEND        0xFE   // 重发命令
#define KB_ERROR         0xFC   // 错误
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%02X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    uint8_t value = 0;
    
    // 为演示目的,模拟不同端口的返回值
    if (port == PPI_PORT_A) {
        // 模拟键盘控制器返回值
        static int count = 0;
        switch (count++ % 5) {
            case 0: value = KB_ACK; break;        // 确认命令
            case 1: value = 0x1C; break;          // Enter键扫描码
            case 2: value = 0x01; break;          // Esc键扫描码
            case 3: value = 0xAA; break;          // 自检通过
            case 4: value = KB_ERROR; break;      // 错误
        }
    } else if (port == PPI_PORT_B) {
        // 模拟系统控制端口
        value = KB_ENABLE | RAM_PARITY;
    }
    
    printf("读端口 0x%02X: 0x%02X
", port, value);
    return value;
}
 
// 初始化PC键盘接口
void init_pc_keyboard() {
    printf("
初始化PC键盘接口...
");
    
    // 配置8255A
    // 端口A为双向(用于键盘数据)
    // 端口B为输出(系统控制)
    // 端口C为输入(配置信息)
    uint8_t control = 0x80 | 0x02;  // 位7=1, 位1=1(端口B为输出)
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为PC键盘接口:
");
    printf("  端口A: 双向(键盘数据)
");
    printf("  端口B: 输出(系统控制)
");
    printf("  端口C: 输入(配置信息)
");
    
    // 设置端口B,启用键盘
    uint8_t port_b = KB_ENABLE | RAM_PARITY;
    outb(PPI_PORT_B, port_b);
}
 
// 向键盘控制器发送命令
bool send_kb_controller_command(uint8_t command) {
    printf("
向键盘控制器发送命令: 0x%02X
", command);
    
    // 在实际系统中,应该等待控制器准备好接收命令
    // 这里简化处理,直接发送
    outb(PPI_PORT_A, command);
    
    // 等待响应(在实际系统中应该轮询状态端口)
    usleep(10000);
    
    // 读取响应
    uint8_t response = inb(PPI_PORT_A);
    
    // 解析响应
    if (command == KC_SELF_TEST) {
        if (response == 0x55) {
            printf("控制器自检通过
");
            return true;
        } else {
            printf("控制器自检失败,返回: 0x%02X
", response);
            return false;
        }
    } else {
        printf("控制器响应: 0x%02X
", response);
        return (response == KB_ACK);
    }
}
 
// 向键盘发送命令
bool send_keyboard_command(uint8_t command) {
    printf("
向键盘发送命令: 0x%02X
", command);
    
    // 在实际系统中,这需要特定的序列和时序
    // 这里简化处理,直接发送到控制器
    outb(PPI_PORT_A, command);
    
    // 等待响应
    usleep(20000);
    
    // 读取响应
    uint8_t response = inb(PPI_PORT_A);
    
    // 检查确认响应
    if (response == KB_ACK) {
        printf("键盘确认命令
");
        return true;
    } else if (response == KB_RESEND) {
        printf("键盘请求重发命令
");
        return false;
    } else {
        printf("键盘返回未知响应: 0x%02X
", response);
        return false;
    }
}
 
// 设置键盘LED状态
bool set_keyboard_leds(bool num_lock, bool caps_lock, bool scroll_lock) {
    printf("
设置键盘LED状态:
");
    printf("  NumLock: %s
", num_lock ? "开" : "关");
    printf("  CapsLock: %s
", caps_lock ? "开" : "关");
    printf("  ScrollLock: %s
", scroll_lock ? "开" : "关");
    
    // 首先发送设置LED命令
    if (!send_keyboard_command(KB_SET_LEDS)) {
        printf("发送LED命令失败
");
        return false;
    }
    
    // 计算LED状态字节
    uint8_t led_status = 0;
    if (scroll_lock) led_status |= 0x01;
    if (num_lock) led_status |= 0x02;
    if (caps_lock) led_status |= 0x04;
    
    // 发送LED状态
    if (!send_keyboard_command(led_status)) {
        printf("发送LED状态失败
");
        return false;
    }
    
    printf("键盘LED设置成功
");
    return true;
}
 
// 复位键盘
bool reset_keyboard() {
    printf("
复位键盘...
");
    
    // 发送复位命令
    if (!send_keyboard_command(KB_RESET)) {
        printf("发送复位命令失败
");
        return false;
    }
    
    // 等待自检完成
    usleep(100000);
    
    // 读取复位后的状态码
    uint8_t status = inb(PPI_PORT_A);
    
    if (status == 0xAA) {
        printf("键盘复位成功,自检通过
");
        return true;
    } else {
        printf("键盘复位失败,返回: 0x%02X
", status);
        return false;
    }
}
 
// 读取键盘扫描码
uint8_t read_keyboard_scancode() {
    printf("
读取键盘扫描码...
");
    
    // 在实际系统中,这通常由中断触发
    // 这里简化处理,直接读取端口
    uint8_t scancode = inb(PPI_PORT_A);
    
    // 解析扫描码(简化版)
    printf("收到扫描码: 0x%02X
", scancode);
    
    // 简单的扫描码解析
    if (scancode < 0x80) {  // 按下事件
        switch (scancode) {
            case 0x01: printf("按键: Esc
"); break;
            case 0x0E: printf("按键: Backspace
"); break;
            case 0x0F: printf("按键: Tab
"); break;
            case 0x1C: printf("按键: Enter
"); break;
            case 0x1D: printf("按键: Ctrl
"); break;
            case 0x2A: printf("按键: Left Shift
"); break;
            case 0x36: printf("按键: Right Shift
"); break;
            case 0x38: printf("按键: Alt
"); break;
            case 0x39: printf("按键: Space
"); break;
            case 0x3A: printf("按键: CapsLock
"); break;
            case 0x45: printf("按键: NumLock
"); break;
            case 0x46: printf("按键: ScrollLock
"); break;
            default:
                if (scancode >= 0x02 && scancode <= 0x0B) {  // 数字键1-0
                    printf("按键: %c
", '0' + ((scancode - 0x02 + 1) % 10));
                } else if (scancode >= 0x10 && scancode <= 0x19) {  // 字母Q-P
                    printf("按键: %c
", 'Q' + (scancode - 0x10));
                } else if (scancode >= 0x1E && scancode <= 0x26) {  // 字母A-L
                    printf("按键: %c
", 'A' + (scancode - 0x1E));
                } else if (scancode >= 0x2C && scancode <= 0x32) {  // 字母Z-M
                    printf("按键: %c
", 'Z' + (scancode - 0x2C));
                } else {
                    printf("未知按键
");
                }
        }
    } else {  // 释放事件
        printf("按键释放: 0x%02X
", scancode - 0x80);
    }
    
    return scancode;
}
 
// 键盘控制器自检
bool keyboard_controller_test() {
    printf("
执行键盘控制器自检...
");
    
    // 发送自检命令
    if (!send_kb_controller_command(KC_SELF_TEST)) {
        printf("控制器自检失败
");
        return false;
    }
    
    // 测试键盘接口
    if (!send_kb_controller_command(KC_KB_TEST)) {
        printf("键盘接口测试失败
");
        return false;
    }
    
    printf("键盘控制器自检完成,通过
");
    return true;
}
 
// 键盘读取循环
void keyboard_loop() {
    printf("
启动键盘读取循环...
");
    printf("(这是一个模拟,会读取预设的按键序列)
");
    
    int iterations = 5;  // 循环次数(实际应用中通常是无限循环)
    
    for (int i = 0; i < iterations; i++) {
        printf("
等待按键输入...
");
        
        // 模拟按键等待
        usleep(500000);
        
        // 读取按键
        uint8_t scancode = read_keyboard_scancode();
        
        // 在实际应用中,这里会处理按键,如输入到缓冲区、执行命令等
    }
}
 
int main() {
    printf("PC键盘控制器接口示例
");
    printf("===================
");
    
    // 初始化PC键盘接口
    init_pc_keyboard();
    
    // 键盘控制器自检
    if (keyboard_controller_test()) {
        // 复位键盘
        if (reset_keyboard()) {
            // 设置键盘LED
            set_keyboard_leds(true, false, false);  // NumLock开,其他关
            
            // 启动键盘读取循环
            keyboard_loop();
        }
    }
    
    return 0;
}

这个示例演示了PC键盘控制器接口的工作原理,包括以下方面:

键盘控制器命令处理:向控制器发送命令并解析响应键盘命令处理:向键盘发送命令(如设置LED、复位等)扫描码读取:从控制器读取键盘扫描码并解析自检和初始化:控制器和键盘的自检与初始化过程

PC键盘接口比简单的矩阵键盘更为复杂,它支持更多的功能:

更丰富的按键集(包括控制键、功能键等)支持按键组合(如Ctrl+Alt+Del)LED指示灯控制自检和错误报告可扩展的命令集

虽然现代PC已经不再使用8255A作为键盘接口,但了解这种经典接口对于理解计算机输入系统的发展历史很有帮助。

11.4 LED数码管显示接口设计

LED数码管是一种常见的数字显示设备,通过8255A可以轻松实现它们的控制接口。LED数码管可以采用静态驱动或动态扫描方式驱动,后者更为常用,因为它可以节省I/O端口。

LED数码管的基本类型

7段数码管:用于显示数字0-9和有限的字母8段数码管:增加了小数点,用于显示数字和小数点16段数码管:可以显示更完整的字母和符号点阵显示:通过矩阵排列的LED点阵显示更丰富的内容

以下是使用8255A控制多个7段LED数码管的示例代码:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
 
// 8255A端口地址
#define PPI_PORT_A  0x40       // 段码输出端口
#define PPI_PORT_B  0x41       // 位选择输出端口
#define PPI_PORT_C  0x42       // 辅助输出端口
#define PPI_CONTROL 0x43       // 控制寄存器
 
// 显示器配置
#define NUM_DIGITS  4          // 4位数码管
#define SCAN_DELAY  5000       // 扫描延迟(微秒)
 
// 7段数码管段码定义(共阴极)
// 段码排列:  GFEDCBA
//            6543210
//
//    AAA
//   F   B
//   F   B
//    GGG
//   E   C
//   E   C
//    DDD  DP
//
const uint8_t seven_segment_map[16] = {
    0x3F, // 0: 0b00111111 - ABCDEF
    0x06, // 1: 0b00000110 - BC
    0x5B, // 2: 0b01011011 - ABDEG
    0x4F, // 3: 0b01001111 - ABCDG
    0x66, // 4: 0b01100110 - BCFG
    0x6D, // 5: 0b01101101 - ACDFG
    0x7D, // 6: 0b01111101 - ACDEFG
    0x07, // 7: 0b00000111 - ABC
    0x7F, // 8: 0b01111111 - ABCDEFG
    0x6F, // 9: 0b01101111 - ABCDFG
    0x77, // A: 0b01110111 - ABCEFG
    0x7C, // b: 0b01111100 - CDEFG
    0x39, // C: 0b00111001 - ADEF
    0x5E, // d: 0b01011110 - BCDEG
    0x79, // E: 0b01111001 - ADEFG
    0x71  // F: 0b01110001 - AEFG
};
 
// 小数点位掩码
#define DP_MASK 0x80  // 位7作为小数点
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%02X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    printf("读端口 0x%02X
", port);
    return 0xFF;  // 默认返回全高电平
}
 
// 初始化8255A作为LED数码管接口
void init_led_display() {
    printf("
初始化8255A作为LED数码管接口...
");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=00: 组A工作在模式0
    // 位4=1: 端口A为输出(段码)
    // 位3=1: 端口C上半部分为输出
    // 位2=0: 组B工作在模式0
    // 位1=1: 端口B为输出(位选择)
    // 位0=1: 端口C下半部分为输出
    uint8_t control = 0x80 | 0x10 | 0x08 | 0x02 | 0x01;
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为LED数码管接口:
");
    printf("  端口A: 输出(7段码 + 小数点)
");
    printf("  端口B: 输出(位选择)
");
    printf("  端口C: 输出(辅助控制)
");
    
    // 初始化所有端口为0(全部关闭)
    outb(PPI_PORT_A, 0x00);
    outb(PPI_PORT_B, 0x00);
    outb(PPI_PORT_C, 0x00);
}
 
// 显示单个数码
void display_digit(int position, uint8_t segment_code) {
    // 验证位置有效性
    if (position < 0 || position >= NUM_DIGITS) {
        printf("错误: 无效的位置 %d
", position);
        return;
    }
    
    // 选择位置(低电平有效)
    uint8_t position_select = ~(1 << position);
    
    // 输出段码和位选择
    outb(PPI_PORT_A, segment_code);
    outb(PPI_PORT_B, position_select);
}
 
// 显示十六进制数字
void display_hex_digit(int position, uint8_t value, bool decimal_point) {
    // 将值限制在0-15范围内
    value &= 0x0F;
    
    // 获取对应的段码
    uint8_t segment_code = seven_segment_map[value];
    
    // 如果需要小数点,添加小数点位
    if (decimal_point) {
        segment_code |= DP_MASK;
    }
    
    // 显示该数字
    display_digit(position, segment_code);
}
 
// 动态扫描显示多位数字(十六进制)
void display_hex_number(uint16_t number, uint8_t dp_position) {
    // 分解数字到各个位
    uint8_t digits[NUM_DIGITS];
    
    for (int i = 0; i < NUM_DIGITS; i++) {
        digits[NUM_DIGITS - 1 - i] = number & 0x0F;
        number >>= 4;
    }
    
    // 打印调试信息
    printf("
显示十六进制数字: 0x");
    for (int i = 0; i < NUM_DIGITS; i++) {
        printf("%X", digits[i]);
    }
    if (dp_position < NUM_DIGITS) {
        printf(" (小数点在位置%d)
", dp_position);
    } else {
        printf(" (无小数点)
");
    }
    
    // 一个扫描周期
    for (int i = 0; i < NUM_DIGITS; i++) {
        // 显示当前位,判断是否需要小数点
        display_hex_digit(i, digits[i], i == dp_position);
        
        // 延迟一段时间
        usleep(SCAN_DELAY);
    }
    
    // 关闭所有段,防止残影
    outb(PPI_PORT_A, 0x00);
    outb(PPI_PORT_B, 0x00);
}
 
// 显示十进制数字(带前导零)
void display_decimal_number(uint16_t number, uint8_t dp_position) {
    // 分解数字到各个位
    uint8_t digits[NUM_DIGITS];
    
    for (int i = 0; i < NUM_DIGITS; i++) {
        digits[NUM_DIGITS - 1 - i] = number % 10;
        number /= 10;
    }
    
    // 打印调试信息
    printf("
显示十进制数字: ");
    for (int i = 0; i < NUM_DIGITS; i++) {
        printf("%d", digits[i]);
        if (i == dp_position) {
            printf(".");
        }
    }
    printf("
");
    
    // 一个扫描周期
    for (int i = 0; i < NUM_DIGITS; i++) {
        // 显示当前位,判断是否需要小数点
        display_hex_digit(i, digits[i], i == dp_position);
        
        // 延迟一段时间
        usleep(SCAN_DELAY);
    }
    
    // 关闭所有段,防止残影
    outb(PPI_PORT_A, 0x00);
    outb(PPI_PORT_B, 0x00);
}
 
// 连续显示一个数字
void continuous_display(uint16_t number, uint8_t dp_position, bool is_hex, int duration_ms) {
    printf("
连续显示%s数: ", is_hex ? "十六进制" : "十进制");
    
    int cycles = duration_ms * 1000 / (SCAN_DELAY * NUM_DIGITS);
    
    for (int i = 0; i < cycles; i++) {
        if (is_hex) {
            display_hex_number(number, dp_position);
        } else {
            display_decimal_number(number, dp_position);
        }
    }
}
 
// 显示简单动画效果
void display_animation(int cycles) {
    printf("
显示动画效果...
");
    
    // 旋转效果
    uint8_t rotation[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40};
    int rot_size = sizeof(rotation) / sizeof(rotation[0]);
    
    for (int c = 0; c < cycles; c++) {
        for (int i = 0; i < rot_size; i++) {
            for (int pos = 0; pos < NUM_DIGITS; pos++) {
                display_digit(pos, rotation[i]);
                usleep(SCAN_DELAY / 4);
            }
        }
    }
    
    // 全亮效果
    for (int c = 0; c < 3; c++) {
        // 全部亮
        for (int pos = 0; pos < NUM_DIGITS; pos++) {
            display_digit(pos, 0xFF);
            usleep(SCAN_DELAY / 4);
        }
        
        // 全部灭
        outb(PPI_PORT_A, 0x00);
        outb(PPI_PORT_B, 0x00);
        usleep(200000);  // 200ms
    }
}
 
// 显示滚动文本(仅支持有限字符集)
void display_scrolling_text(const char *text) {
    printf("
显示滚动文本: "%s"
", text);
    
    // 字符到7段



// 显示滚动文本(仅支持有限字符集)
void display_scrolling_text(const char *text) {
    printf("
显示滚动文本: "%s"
", text);
    
    // 字符到7段码的映射表(仅支持部分字符)
    const uint8_t char_to_segment[128] = {
        0x00, // NUL (空白)
        0x00, // SOH
        0x00, // STX
        0x00, // ETX
        0x00, // EOT
        0x00, // ENQ
        0x00, // ACK
        0x00, // BEL
        0x00, // BS
        0x00, // HT
        0x00, // LF
        0x00, // VT
        0x00, // FF
        0x00, // CR
        0x00, // SO
        0x00, // SI
        0x00, // DLE
        0x00, // DC1
        0x00, // DC2
        0x00, // DC3
        0x00, // DC4
        0x00, // NAK
        0x00, // SYN
        0x00, // ETB
        0x00, // CAN
        0x00, // EM
        0x00, // SUB
        0x00, // ESC
        0x00, // FS
        0x00, // GS
        0x00, // RS
        0x00, // US
        0x00, // 空格
        0x00, // !
        0x00, // "
        0x00, // #
        0x00, // $
        0x00, // %
        0x00, // &
        0x00, // '
        0x00, // (
        0x00, // )
        0x00, // *
        0x00, // +
        0x00, // ,
        0x40, // - (中横线)
        0x80, // . (小数点)
        0x00, // /
        0x3F, // 0
        0x06, // 1
        0x5B, // 2
        0x4F, // 3
        0x66, // 4
        0x6D, // 5
        0x7D, // 6
        0x07, // 7
        0x7F, // 8
        0x6F, // 9
        0x00, // :
        0x00, // ;
        0x00, // <
        0x48, // = (两个横线)
        0x00, // >
        0x00, // ?
        0x00, // @
        0x77, // A
        0x7C, // b
        0x39, // C
        0x5E, // d
        0x79, // E
        0x71, // F
        0x3D, // G
        0x76, // H
        0x30, // I (两侧显示)
        0x1E, // J
        0x00, // K (无法很好显示)
        0x38, // L
        0x00, // M (无法很好显示)
        0x54, // n
        0x3F, // O
        0x73, // P
        0x67, // q
        0x50, // r
        0x6D, // S
        0x78, // t
        0x3E, // U
        0x00, // V (无法很好显示)
        0x00, // W (无法很好显示)
        0x00, // X (无法很好显示)
        0x6E, // y
        0x00, // Z (无法很好显示)
        0x00, // [
        0x00, // 
        0x00, // ]
        0x00, // ^
        0x08, // _ (下横线)
        0x00, // `
        0x77, // a (与A相同)
        0x7C, // b
        0x58, // c (小写)
        0x5E, // d
        0x79, // e (与E相同)
        0x71, // f (与F相同)
        0x3D, // g (与G相同)
        0x74, // h (小写)
        0x10, // i (右侧显示)
        0x1E, // j (与J相同)
        0x00, // k (无法很好显示)
        0x38, // l (与L相同)
        0x00, // m (无法很好显示)
        0x54, // n
        0x5C, // o (小写)
        0x73, // p (与P相同)
        0x67, // q
        0x50, // r
        0x6D, // s (与S相同)
        0x78, // t
        0x1C, // u (小写)
        0x00, // v (无法很好显示)
        0x00, // w (无法很好显示)
        0x00, // x (无法很好显示)
        0x6E, // y
        0x00, // z (无法很好显示)
        0x00, // {
        0x00, // |
        0x00, // }
        0x00, // ~
        0x00  // DEL
    };
    
    size_t text_len = strlen(text);
    
    if (text_len == 0) {
        printf("错误: 空文本
");
        return;
    }
    
    // 创建一个比原文本长的缓冲区,在末尾添加空白以实现滚动效果
    size_t buffer_len = text_len + NUM_DIGITS;
    uint8_t *segment_buffer = (uint8_t *)malloc(buffer_len * sizeof(uint8_t));
    
    if (!segment_buffer) {
        printf("错误: 内存分配失败
");
        return;
    }
    
    // 将文本转换为段码
    for (size_t i = 0; i < text_len; i++) {
        segment_buffer[i] = char_to_segment[(unsigned char)text[i]];
    }
    
    // 末尾填充空白
    for (size_t i = text_len; i < buffer_len; i++) {
        segment_buffer[i] = 0x00;
    }
    
    // 滚动显示
    for (size_t start_pos = 0; start_pos <= text_len; start_pos++) {
        for (int repeat = 0; repeat < 10; repeat++) { // 每个位置重复10次以减缓滚动速度
            // 一个扫描周期
            for (int digit_pos = 0; digit_pos < NUM_DIGITS; digit_pos++) {
                size_t text_pos = start_pos + digit_pos;
                uint8_t segments = (text_pos < buffer_len) ? segment_buffer[text_pos] : 0x00;
                display_digit(digit_pos, segments);
                usleep(SCAN_DELAY);
            }
        }
    }
    
    free(segment_buffer);
}
 
// 自检程序
void self_test() {
    printf("
执行LED显示器自检...
");
    
    // 测试所有段
    printf("测试所有段...
");
    for (int segment = 0; segment < 8; segment++) {
        uint8_t segment_mask = 1 << segment;
        
        for (int position = 0; position < NUM_DIGITS; position++) {
            display_digit(position, segment_mask);
            usleep(100000); // 延时100ms
        }
    }
    
    // 测试所有位置
    printf("测试所有位置...
");
    for (int position = 0; position < NUM_DIGITS; position++) {
        display_digit(position, 0xFF); // 全亮
        usleep(200000); // 延时200ms
    }
    
    // 测试所有数字
    printf("测试所有数字...
");
    for (int digit = 0; digit <= 0xF; digit++) {
        for (int position = 0; position < NUM_DIGITS; position++) {
            display_hex_digit(position, digit, false);
        }
        usleep(300000); // 延时300ms
    }
    
    // 全亮测试
    printf("全亮测试...
");
    for (int i = 0; i < 3; i++) {
        // 全部点亮
        for (int position = 0; position < NUM_DIGITS; position++) {
            display_digit(position, 0xFF);
        }
        usleep(500000); // 延时500ms
        
        // 全部熄灭
        outb(PPI_PORT_A, 0x00);
        outb(PPI_PORT_B, 0x00);
        usleep(500000); // 延时500ms
    }
    
    printf("自检完成
");
}
 
int main() {
    printf("8255A LED数码管接口示例
");
    printf("=====================
");
    
    // 初始化LED数码管接口
    init_led_display();
    
    // 执行自检
    self_test();
    
    // 显示十六进制数字
    continuous_display(0xABCD, 2, true, 2000); // 显示0xABCD,小数点在第3位,持续2秒
    
    // 显示十进制数字
    continuous_display(1234, 1, false, 2000); // 显示1234,小数点在第2位,持续2秒
    
    // 计数器效果(十进制)
    printf("
计数器效果...
");
    for (int i = 0; i <= 9999; i += 111) {
        continuous_display(i, NUM_DIGITS, false, 300); // 无小数点
    }
    
    // 显示动画效果
    display_animation(2);
    
    // 显示滚动文本
    display_scrolling_text("HELLO 8255A");
    
    // 清空显示
    outb(PPI_PORT_A, 0x00);
    outb(PPI_PORT_B, 0x00);
    
    printf("
LED数码管示例结束
");
    
    return 0;
}

上述代码展示了如何使用8255A接口来控制LED数码管显示。主要功能包括:

初始化配置:将8255A的三个端口配置为输出模式,端口A用于段码输出,端口B用于位选择,端口C作为辅助控制数字显示:支持十六进制和十进制数字显示动态扫描:通过快速扫描所有位置,创建多位数字的显示效果特殊效果:包括动画、滚动文本等自检程序:逐段、逐位测试以验证硬件正常

这种LED数码管接口设计的关键点包括:

动态扫描技术:利用人眼视觉暂留效应,通过快速扫描显示多个数字段码映射:使用查找表将数字或字符映射到相应的LED段码位选择控制:通过位选择信号控制当前激活哪一位数码管

在实际应用中,这种接口可用于:

数字计数器和时钟显示温度、电压等参数测量结果显示简单人机交互界面状态和错误代码显示

虽然现代设备多使用LCD、OLED等高级显示技术,但LED数码管因其简单、可靠且在强光下可见的特点,仍在许多工业和消费电子设备中被广泛使用。

11.5 并行打印机接口实现技术

11.5.1 打印机接口信号分析

并行打印机接口(Centronics接口)是早期计算机系统中最常见的打印机连接方式。通过8255A可以实现完整的并行打印机接口,控制各种打印机设备。

Centronics并行打印机接口的主要信号

数据线(DATA0-DATA7):8位并行数据传输控制信号
STROBE(低电平有效):数据有效信号,表示数据已经准备好INIT(低电平有效):初始化打印机AUTO FEED XT(低电平有效):自动换行SELECT IN(低电平有效):选择打印机
状态信号
ACK(低电平有效):确认信号,打印机已接收数据BUSY(高电平有效):打印机忙碌信号PAPER END(高电平有效):缺纸信号SELECT(高电平有效):打印机在线信号ERROR(低电平有效):错误信号

以下是并行打印机接口信号的分析代码:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
 
// 打印机接口信号定义
// 数据端口位定义
#define DATA0       0x01
#define DATA1       0x02
#define DATA2       0x04
#define DATA3       0x08
#define DATA4       0x10
#define DATA5       0x20
#define DATA6       0x40
#define DATA7       0x80
 
// 状态端口位定义
#define BUSY        0x80    // 位7: 打印机忙
#define ACK         0x40    // 位6: 确认信号(低电平有效)
#define PAPER_END   0x20    // 位5: 缺纸
#define SELECT      0x10    // 位4: 打印机选择(在线)
#define ERROR       0x08    // 位3: 错误(低电平有效)
 
// 控制端口位定义
#define STROBE      0x01    // 位0: 数据有效(低电平有效)
#define AUTO_FEED   0x02    // 位1: 自动换行(低电平有效)
#define INIT        0x04    // 位2: 初始化(低电平有效)
#define SELECT_IN   0x08    // 位3: 选择打印机(低电平有效)
 
// 分析并行打印机状态
void analyze_printer_status(uint8_t status_byte) {
    printf("
打印机状态分析 (0x%02X):
", status_byte);
    printf("  BUSY: %s
", (status_byte & BUSY) ? "是 (打印机忙)" : "否 (打印机就绪)");
    printf("  ACK: %s
", (status_byte & ACK) ? "否 (未确认)" : "是 (已确认)");
    printf("  PAPER END: %s
", (status_byte & PAPER_END) ? "是 (缺纸)" : "否 (有纸)");
    printf("  SELECT: %s
", (status_byte & SELECT) ? "是 (打印机在线)" : "否 (打印机离线)");
    printf("  ERROR: %s
", (status_byte & ERROR) ? "否 (无错误)" : "是 (有错误)");
    
    // 综合分析
    if ((status_byte & ERROR) == 0) {
        printf("错误状态: 打印机报告错误
");
    }
    else if ((status_byte & SELECT) == 0) {
        printf("错误状态: 打印机离线
");
    }
    else if (status_byte & PAPER_END) {
        printf("错误状态: 打印机缺纸
");
    }
    else if (status_byte & BUSY) {
        printf("状态: 打印机正忙
");
    }
    else {
        printf("状态: 打印机就绪,可以接收数据
");
    }
}
 
// 生成打印机控制信号
uint8_t generate_printer_control(bool strobe_active, bool auto_feed, bool init_printer, bool select_printer) {
    uint8_t control = 0;
    
    // 注意:这些信号都是低电平有效
    if (!strobe_active) control |= STROBE;
    if (!auto_feed) control |= AUTO_FEED;
    if (!init_printer) control |= INIT;
    if (!select_printer) control |= SELECT_IN;
    
    printf("
生成打印机控制信号 (0x%02X):
", control);
    printf("  STROBE: %s
", (control & STROBE) ? "不激活" : "激活 (数据有效)");
    printf("  AUTO FEED: %s
", (control & AUTO_FEED) ? "禁用" : "启用 (自动换行)");
    printf("  INIT: %s
", (control & INIT) ? "不激活" : "激活 (初始化打印机)");
    printf("  SELECT IN: %s
", (control & SELECT_IN) ? "不激活" : "激活 (选择打印机)");
    
    return control;
}
 
// 打印机数据传输过程分析
void analyze_data_transfer_process() {
    printf("
打印机数据传输过程分析:
");
    printf("1. 主机检查打印机状态(检查BUSY信号)
");
    printf("2. 如果打印机就绪(BUSY=0),主机将数据放到数据线上
");
    printf("3. 主机产生STROBE信号(高到低再到高)
");
    printf("4. 打印机检测到STROBE下降沿,将BUSY置1
");
    printf("5. 打印机读取数据,然后产生ACK信号(高到低再到高)
");
    printf("6. 打印机完成数据处理,将BUSY置0
");
    printf("7. 主机检测到ACK信号,知道打印机已接收数据
");
    printf("8. 循环重复步骤1-7,直到所有数据传输完成
");
}
 
// 打印机初始化序列分析
void analyze_printer_initialization() {
    printf("
打印机初始化序列分析:
");
    printf("1. 主机将INIT信号置低(至少50微秒)
");
    printf("2. 打印机执行复位操作(清除缓冲区、将打印头移动到起始位置)
");
    printf("3. 主机将INIT信号恢复为高
");
    printf("4. 打印机完成初始化,可以接收数据
");
    
    printf("
初始化时间注意事项:
");
    printf("- INIT低电平持续时间至少需要50微秒
");
    printf("- 打印机完成初始化可能需要几百毫秒到几秒
");
    printf("- 初始化后需要等待BUSY信号变为低电平
");
}
 
// 打印机错误处理分析
void analyze_error_handling() {
    printf("
打印机错误处理分析:
");
    printf("1. 缺纸错误(PAPER END=1):
");
    printf("   - 系统应提示用户添加纸张
");
    printf("   - 等待PAPER END返回0后继续打印
");
    
    printf("2. 打印机错误(ERROR=0):
");
    printf("   - 可能是卡纸、打印头故障等
");
    printf("   - 系统应提示用户检查打印机
");
    printf("   - 可能需要复位打印机(INIT信号)
");
    
    printf("3. 打印机离线(SELECT=0):
");
    printf("   - 可能是用户按下了打印机的离线按钮
");
    printf("   - 系统应提示用户将打印机设置为在线状态
");
    
    printf("4. 打印机一直忙(BUSY一直为1):
");
    printf("   - 可能是通信故障或打印机内部问题
");
    printf("   - 系统可以实现超时机制
");
    printf("   - 尝试初始化打印机(INIT信号)
");
}
 
// 打印机信号时序模拟
void simulate_printer_timing() {
    printf("
模拟打印机数据传输时序:
");
    
    // 初始状态
    uint8_t status = 0x18;  // ~ERROR=1, SELECT=1, 其他=0
    uint8_t control = 0x0F; // 所有控制信号不激活(高电平)
    uint8_t data = 0x41;    // ASCII 'A'
    
    printf("
初始状态:
");
    printf("数据: 0x%02X (未发送)
", data);
    analyze_printer_status(status);
    
    // 第1步: 检查打印机就绪
    printf("
步骤1: 检查打印机就绪
");
    if (!(status & BUSY)) {
        printf("打印机就绪,可以发送数据
");
    } else {
        printf("打印机忙,等待...
");
        // 模拟等待
        status &= ~BUSY;
        printf("打印机现在就绪
");
    }
    
    // 第2步: 放置数据
    printf("
步骤2: 放置数据到数据线
");
    printf("数据线: 0x%02X (ASCII '%c')
", data, data);
    
    // 第3步: 生成STROBE信号
    printf("
步骤3: 生成STROBE信号
");
    control &= ~STROBE;  // STROBE置低
    printf("STROBE=0 (激活)
");
    usleep(1000);  // 等待1微秒
    
    // 第4步: 打印机响应
    printf("
步骤4: 打印机将BUSY置1
");
    status |= BUSY;
    analyze_printer_status(status);
    
    // 第5步: 结束STROBE信号
    printf("
步骤5: 结束STROBE信号
");
    control |= STROBE;  // STROBE置高
    printf("STROBE=1 (不激活)
");
    
    // 第6步: 打印机产生ACK信号
    printf("
步骤6: 打印机产生ACK信号
");
    status &= ~ACK;  // ACK置低
    printf("ACK=0 (确认)
");
    usleep(5000);  // 等待5微秒
    status |= ACK;  // ACK置高
    printf("ACK=1 (结束确认)
");
    
    // 第7步: 打印机处理完数据,BUSY置低
    printf("
步骤7: 打印机处理完数据,BUSY置低
");
    status &= ~BUSY;
    analyze_printer_status(status);
    
    printf("
数据传输完成
");
}
 
int main() {
    printf("并行打印机接口信号分析
");
    printf("===================
");
    
    // 分析打印机状态
    analyze_printer_status(0x18); // ~ERROR=1, SELECT=1, 其他=0(正常状态)
    analyze_printer_status(0x98); // BUSY=1, ~ERROR=1, SELECT=1, 其他=0(忙碌状态)
    analyze_printer_status(0x38); // PAPER_END=1, ~ERROR=1, SELECT=1, 其他=0(缺纸状态)
    analyze_printer_status(0x08); // ~ERROR=1, 其他=0(离线状态)
    analyze_printer_status(0x10); // SELECT=1, 其他=0(错误状态)
    
    // 生成控制信号
    generate_printer_control(false, true, true, true);  // 只激活STROBE
    generate_printer_control(true, false, true, true);  // 只激活AUTO_FEED
    generate_printer_control(true, true, false, true);  // 只激活INIT
    generate_printer_control(true, true, true, false);  // 只激活SELECT_IN
    
    // 分析数据传输过程
    analyze_data_transfer_process();
    
    // 分析初始化序列
    analyze_printer_initialization();
    
    // 分析错误处理
    analyze_error_handling();
    
    // 模拟打印机时序
    simulate_printer_timing();
    
    return 0;
}

这段代码详细分析了并行打印机接口的各种信号及其交互过程,包括:

状态信号分析:解析打印机状态寄存器中的各个位,了解打印机的当前状态控制信号生成:根据需要生成适当的控制信号来操作打印机数据传输过程:描述完整的数据传输流程和握手协议初始化序列:说明如何正确初始化打印机错误处理:分析常见的打印机错误情况及处理方法时序模拟:模拟一个完整的数据传输时序

理解这些信号和时序对于实现可靠的打印机接口至关重要,无论是基于8255A还是其他并行接口芯片。

11.5.2 打印机适配器设计

打印机适配器是连接计算机和打印机的接口电路,通常基于8255A等并行I/O芯片实现。它负责控制信号转换、电平匹配和数据缓冲等功能。

以下是一个基于8255A的打印机适配器设计示例:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
 
// 8255A端口地址
#define PPI_PORT_A  0x378       // 数据端口(数据输出)
#define PPI_PORT_B  0x379       // 状态端口(状态输入)
#define PPI_PORT_C  0x37A       // 控制端口(控制输出)
#define PPI_CONTROL 0x37B       // 控制寄存器
 
// 打印机状态位(端口B)
#define BUSY        0x80    // 位7: 打印机忙
#define ACK         0x40    // 位6: 确认信号(低电平有效)
#define PAPER_END   0x20    // 位5: 缺纸
#define SELECT      0x10    // 位4: 打印机选择(在线)
#define ERROR       0x08    // 位3: 错误(低电平有效)
 
// 打印机控制位(端口C)
#define STROBE      0x01    // 位0: 数据有效(低电平有效)
#define AUTO_FEED   0x02    // 位1: 自动换行(低电平有效)
#define INIT        0x04    // 位2: 初始化(低电平有效)
#define SELECT_IN   0x08    // 位3: 选择打印机(低电平有效)
 
// 打印机适配器配置
typedef struct {
    bool auto_linefeed;         // 自动换行功能
    bool bidirectional;         // 双向模式
    int timeout_ms;             // 超时(毫秒)
    bool ignore_paper_end;      // 忽略缺纸信号
    int strobe_duration_us;     // STROBE信号持续时间(微秒)
} PrinterAdapterConfig;
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("写端口 0x%03X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    uint8_t value = 0;
    
    // 为演示目的,模拟不同状态的打印机
    static int counter = 0;
    counter++;
    
    if (port == PPI_PORT_B) {
        // 模拟打印机状态变化
        if (counter % 5 == 0) {
            value = 0x18;  // 正常状态
        } else if (counter % 5 == 1) {
            value = 0x98;  // 忙碌状态
        } else if (counter % 5 == 2) {
            value = 0x38;  // 缺纸状态
        } else if (counter % 5 == 3) {
            value = 0x08;  // 离线状态
        } else {
            value = 0x10;  // 错误状态
        }
    }
    
    printf("读端口 0x%03X: 0x%02X
", port, value);
    return value;
}
 
// 初始化8255A作为打印机适配器
void init_printer_adapter() {
    printf("
初始化8255A作为打印机适配器...
");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=00: 组A工作在模式0
    // 位4=1: 端口A为输出(数据)
    // 位3=0: 端口C上半部分为输入(未使用)
    // 位2=0: 组B工作在模式0
    // 位1=0: 端口B为输入(状态)
    // 位0=1: 端口C下半部分为输出(控制)
    uint8_t control = 0x80 | 0x10 | 0x01;
    outb(PPI_CONTROL, control);
    
    printf("8255A配置为打印机适配器:
");
    printf("  端口A: 输出(打印机数据)
");
    printf("  端口B: 输入(打印机状态)
");
    printf("  端口C下半部分: 输出(打印机控制)
");
    
    // 初始化控制信号
    // 默认状态: STROBE=1, AUTO_FEED=1, INIT=1, SELECT_IN=0
    // 这使打印机处于选中状态,但没有激活其他信号
    outb(PPI_PORT_C, STROBE | AUTO_FEED | INIT);
}
 
// 检查打印机状态
bool check_printer_status(PrinterAdapterConfig *config, bool *needs_action) {
    printf("
检查打印机状态...
");
    
    uint8_t status = inb(PPI_PORT_B);
    *needs_action = false;
    
    printf("打印机状态: 0x%02X
", status);
    
    // 检查错误状态
    if ((status & ERROR) == 0) {
        printf("错误: 打印机报告一般错误
");
        *needs_action = true;
        return false;
    }
    
    // 检查打印机是否在线
    if ((status & SELECT) == 0) {
        printf("错误: 打印机离线
");
        *needs_action = true;
        return false;
    }
    
    // 检查是否缺纸
    if ((status & PAPER_END) && !config->ignore_paper_end) {
        printf("错误: 打印机缺纸
");
        *needs_action = true;
        return false;
    }
    
    // 检查是否忙碌
    if (status & BUSY) {
        printf("状态: 打印机忙
");
        return false;
    }
    
    printf("状态: 打印机就绪
");
    return true;
}
 
// 重置打印机
bool reset_printer() {
    printf("
重置打印机...
");
    
    // 获取当前控制寄存器值
    uint8_t control = inb(PPI_PORT_C);
    
    // 生成INIT信号(低电平脉冲)
    control &= ~INIT;  // INIT置低
    outb(PPI_PORT_C, control);
    
    // INIT信号至少保持50微秒
    usleep(100000);  // 实际上使用100毫秒,确保足够长
    
    // 恢复INIT信号
    control |= INIT;  // INIT置高
    outb(PPI_PORT_C, control);
    
    printf("等待打印机完成初始化...
");
    
    // 等待打印机完成初始化(BUSY变为低)
    int timeout_counter = 0;
    uint8_t status;
    do {
        usleep(100000);  // 等待100毫秒
        status = inb(PPI_PORT_B);
        timeout_counter++;
        
        if (timeout_counter > 30) {  // 最多等待3秒
            printf("错误: 初始化超时
");
            return false;
        }
    } while (status & BUSY);
    
    printf("打印机初始化完成
");
    return true;
}
 
// 设置自动换行
void set_auto_linefeed(bool enabled) {
    printf("
%s自动换行...
", enabled ? "启用" : "禁用");
    
    // 读取当前控制寄存器值
    uint8_t control = inb(PPI_PORT_C);
    
    // 修改AUTO_FEED位
    if (enabled) {
        control &= ~AUTO_FEED;  // AUTO_FEED=0(启用)
    } else {
        control |= AUTO_FEED;   // AUTO_FEED=1(禁用)
    }
    
    // 写回控制寄存器
    outb(PPI_PORT_C, control);
    
    printf("自动换行现在已%s
", enabled ? "启用" : "禁用");
}
 
// 发送单个字符到打印机
bool send_char_to_printer(char c, PrinterAdapterConfig *config) {
    printf("
发送字符 '%c' (0x%02X) 到打印机...
", c, (uint8_t)c);
    
    // 等待打印机就绪
    int timeout_counter = 0;
    bool needs_action;
    
    while (!check_printer_status(config, &needs_action)) {
        if (needs_action) {
            printf("打印机需要注意,中断发送
");
            return false;
        }
        
        usleep(10000);  // 等待10毫秒
        timeout_counter++;
        
        if (timeout_counter * 10 > config->timeout_ms) {
            printf("错误: 等待打印机就绪超时
");
            return false;
        }
    }
    
    // 将数据发送到数据端口
    outb(PPI_PORT_A, (uint8_t)c);
    
    // 获取当前控制寄存器值
    uint8_t control = inb(PPI_PORT_C);
    
    // 生成STROBE信号(低电平脉冲)
    control &= ~STROBE;  // STROBE置低
    outb(PPI_PORT_C, control);
    
    // 保持STROBE信号足够长的时间
    usleep(config->strobe_duration_us);
    
    // 恢复STROBE信号
    control |= STROBE;  // STROBE置高
    outb(PPI_PORT_C, control);
    
    // 等待ACK信号(如果启用了双向模式)
    if (config->bidirectional) {
        printf("等待ACK信号...
");
        
        timeout_counter = 0;
        bool ack_received = false;
        
        do {
            usleep(1000);  // 等待1毫秒
            uint8_t status = inb(PPI_PORT_B);
            
            // 检查ACK信号(低电平有效)
            if (!(status & ACK)) {
                ack_received = true;
                break;
            }
            
            timeout_counter++;
        } while (timeout_counter * 1 <= config->timeout_ms);
        
        if (!ack_received) {
            printf("错误: 等待ACK超时
");
            return false;
        }
        
        printf("收到ACK信号
");
    }
    
    printf("字符发送成功
");
    return true;
}
 
// 向打印机发送字符串
bool print_string(const char *str, PrinterAdapterConfig *config) {
    printf("
打印字符串: "%s"
", str);
    
    size_t len = strlen(str);
    for (size_t i = 0; i < len; i++) {
        if (!send_char_to_printer(str[i], config)) {
            printf("打印中断于字符 %zu
", i);
            return false;
        }
        
        // 如果不是自动换行,并且当前字符是回车,则手动发送换行
        if (!config->auto_linefeed && str[i] == '
') {
            if (!send_char_to_printer('
', config)) {
                printf("发送换行符失败
");
                return false;
            }
        }
    }
    
    printf("字符串打印完成
");
    return true;
}
 
// 创建并配置打印机适配器
PrinterAdapterConfig* create_printer_adapter_config() {
    PrinterAdapterConfig *config = (PrinterAdapterConfig*)malloc(sizeof(PrinterAdapterConfig));
    
    if (!config) {
        printf("错误: 内存分配失败
");
        return NULL;
    }
    
    // 默认配置
    config->auto_linefeed = false;
    config->bidirectional = true;
    config->timeout_ms = 5000;  // 5秒超时
    config->ignore_paper_end = false;
    config->strobe_duration_us = 1000;  // 1毫秒
    
    printf("
创建打印机适配器配置:
");
    printf("  自动换行: %s
", config->auto_linefeed ? "启用" : "禁用");
    printf("  双向模式: %s
", config->bidirectional ? "启用" : "禁用");
    printf("  超时: %d 毫秒
", config->timeout_ms);
    printf("  忽略缺纸: %s
", config->ignore_paper_end ? "是" : "否");
    printf("  STROBE持续时间: %d 微秒
", config->strobe_duration_us);
    
    return config;
}
 
// 打印测试页
bool print_test_page(PrinterAdapterConfig *config) {
    printf("
打印测试页...
");
    
    // 初始化打印机
    if (!reset_printer()) {
        printf("打印机初始化失败,无法打印测试页
");
        return false;
    }
    
    // 测试页标题
    const char *header = "

8255A打印机适配器测试页
";
    const char *separator = "===========================

";
    
    if (!print_string(header, config)) {
        return false;
    }
    
    if (!print_string(separator, config)) {
        return false;
    }
    
    // 打印字母表
    const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ
";
    const char *alphabet2 = "abcdefghijklmnopqrstuvwxyz

";
    
    if (!print_string(alphabet, config)) {
        return false;
    }
    
    if (!print_string(alphabet2, config)) {
        return false;
    }
    
    // 打印数字
    const char *numbers = "0123456789

";
    
    if (!print_string(numbers, config)) {
        return false;
    }
    
    // 打印特殊字符
    const char *special = "!@#$%^&*()_+-={}[]|:;"'<>,.?/

";
    
    if (!print_string(special, config)) {
        return false;
    }
    
    // 结束语
    const char *footer = "测试页打印完成




";
    
    if (!print_string(footer, config)) {
        return false;
    }
    
    printf("测试页打印完成
");
    return true;
}

// 打印适配器配置界面
void configure_printer_adapter(PrinterAdapterConfig *config) {
    printf("
打印适配器配置界面
");
    printf("====================
");
    
    // 这里应该有一个用户输入界面来修改配置
    // 为简化演示,我们只显示配置并进行一些固定的修改
    
    printf("当前配置:
");
    printf("1. 自动换行: %s
", config->auto_linefeed ? "启用" : "禁用");
    printf("2. 双向模式: %s
", config->bidirectional ? "启用" : "禁用");
    printf("3. 超时: %d 毫秒
", config->timeout_ms);
    printf("4. 忽略缺纸: %s
", config->ignore_paper_end ? "是" : "否");
    printf("5. STROBE持续时间: %d 微秒
", config->strobe_duration_us);
    
    printf("
修改配置...
");
    
    // 修改自动换行设置
    config->auto_linefeed = !config->auto_linefeed;
    printf("自动换行设置已切换为: %s
", config->auto_linefeed ? "启用" : "禁用");
    
    // 应用自动换行设置到硬件
    set_auto_linefeed(config->auto_linefeed);
    
    // 增加超时时间
    config->timeout_ms += 1000;
    printf("超时时间已增加到: %d 毫秒
", config->timeout_ms);
    
    printf("
配置已更新
");
}

int main() {
    printf("8255A打印机适配器设计
");
    printf("====================
");
    
    // 初始化8255A作为打印机适配器
    init_printer_adapter();
    
    // 创建打印机适配器配置
    PrinterAdapterConfig *config = create_printer_adapter_config();
    
    if (!config) {
        return 1;
    }
    
    // 配置打印机适配器
    configure_printer_adapter(config);
    
    // 检查打印机状态
    bool needs_action;
    bool printer_ready = check_printer_status(config, &needs_action);
    
    if (printer_ready) {
        // 打印测试页
        print_test_page(config);
    } else {
        if (needs_action) {
            printf("打印机需要人工干预
");
        } else {
            // 打印机暂时忙,等待一段时间
            printf("打印机暂时忙,等待...
");
            usleep(1000000);  // 等待1秒
            
            // 再次检查状态
            printer_ready = check_printer_status(config, &needs_action);
            
            if (printer_ready) {
                print_test_page(config);
            } else {
                printf("打印机仍未就绪,取消打印
");
            }
        }
    }
    
    // 复位打印机
    reset_printer();
    
    // 释放资源
    free(config);
    
    printf("
打印适配器示例结束
");
    
    return 0;
}

这段代码展示了一个完整的打印机适配器设计,包括以下关键功能:

初始化:配置8255A的端口和打印机的初始状态状态检测:读取并解析打印机状态寄存器,识别不同的错误条件打印功能:发送单个字符和完整字符串到打印机控制功能:复位打印机、设置自动换行等配置界面:允许用户配置适配器参数测试页功能:生成并打印测试页

打印机适配器设计需要考虑多种因素:

信号时序:确保数据和控制信号的正确时序错误处理:适当处理缺纸、离线等错误超时机制:避免程序无限等待配置选项:提供灵活的配置选项以适应不同打印机双向通信:利用ACK等信号实现可靠的双向通信

虽然现代系统大多使用USB打印机接口,但并行打印机接口的原理和设计思想仍然值得学习,尤其是对于嵌入式系统和工业控制应用。

11.5.3 打印机驱动程序设计

打印机驱动程序是连接应用程序和打印机硬件的软件层,它负责将高级打印命令转换为打印机能够理解的底层指令。对于并行打印机,8255A接口的驱动程序需要处理数据格式转换、页面布局、字符集映射和打印控制等功能。

以下是一个基于8255A的简易打印机驱动程序设计:

c



#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
 
// 打印机硬件接口(简化版)
typedef struct {
    uint16_t data_port;         // 数据端口地址
    uint16_t status_port;       // 状态端口地址
    uint16_t control_port;      // 控制端口地址
} PrinterHardware;
 
// 打印机状态结构
typedef struct {
    bool online;                // 打印机是否在线
    bool paper_available;       // 是否有纸
    bool busy;                  // 是否忙
    bool error;                 // 是否有错误
    int error_code;             // 错误代码
} PrinterStatus;
 
// 页面设置
typedef struct {
    int page_width;             // 页面宽度(字符数)
    int page_length;            // 页面长度(行数)
    int left_margin;            // 左边距(字符数)
    int top_margin;             // 上边距(行数)
    bool auto_linefeed;         // 自动换行
} PageSettings;
 
// 字体设置
typedef struct {
    bool bold;                  // 粗体
    bool italic;                // 斜体
    bool underline;             // 下划线
    int font_size;              // 字体大小(1-5)
    bool condensed;             // 压缩字体
    int char_spacing;           // 字符间距
    int line_spacing;           // 行间距
} FontSettings;
 
// 打印任务信息
typedef struct {
    char *document_name;        // 文档名称
    int total_pages;            // 总页数
    int current_page;           // 当前页
    int copies;                 // 份数
    bool collate;               // 是否逐份打印
} PrintJobInfo;
 
// 打印机驱动结构
typedef struct {
    PrinterHardware hw;         // 硬件接口
    PrinterStatus status;       // 打印机状态
    PageSettings page;          // 页面设置
    FontSettings font;          // 字体设置
    PrintJobInfo job;           // 打印任务信息
    char *buffer;               // 打印缓冲区
    size_t buffer_size;         // 缓冲区大小
    size_t buffer_pos;          // 当前缓冲区位置
} PrinterDriver;
 
// 打印机命令(基于ESC/P标准,简化版)
#define CMD_ESC         0x1B    // Escape字符
#define CMD_LF          0x0A    // 换行
#define CMD_CR          0x0D    // 回车
#define CMD_FF          0x0C    // 换页
 
// 模拟I/O操作
void outb(uint16_t port, uint8_t value) {
    printf("[I/O] 写端口 0x%03X: 0x%02X
", port, value);
}
 
uint8_t inb(uint16_t port) {
    // 模拟不同端口的返回值
    uint8_t value;
    
    static int status_counter = 0;
    status_counter++;
    
    if ((port & 0xFF) == 0x79) {  // 状态端口
        // 模拟不同状态
        switch (status_counter % 10) {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
                value = 0x18;  // 正常状态 (在线 + 无错误)
                break;
            case 7:
                value = 0x98;  // 忙 (忙 + 在线 + 无错误)
                break;
            case 8:
                value = 0x38;  // 缺纸 (缺纸 + 在线 + 无错误)
                break;
            case 9:
                value = 0x10;  // 错误 (在线 + 有错误)
                break;
        }
    } else {
        value = 0x00;  // 其他端口
    }
    
    printf("[I/O] 读端口 0x%03X: 0x%02X
", port, value);
    return value;
}
 
// 创建打印机驱动
PrinterDriver* create_printer_driver() {
    PrinterDriver *driver = (PrinterDriver*)malloc(sizeof(PrinterDriver));
    
    if (!driver) {
        printf("错误: 内存分配失败
");
        return NULL;
    }
    
    // 初始化硬件接口
    driver->hw.data_port = 0x378;       // 标准LPT1数据端口
    driver->hw.status_port = 0x379;     // 标准LPT1状态端口
    driver->hw.control_port = 0x37A;    // 标准LPT1控制端口
    
    // 初始化状态
    driver->status.online = false;
    driver->status.paper_available = false;
    driver->status.busy = false;
    driver->status.error = false;
    driver->status.error_code = 0;
    
    // 初始化页面设置
    driver->page.page_width = 80;       // 80列
    driver->page.page_length = 66;      // 66行
    driver->page.left_margin = 0;
    driver->page.top_margin = 0;
    driver->page.auto_linefeed = true;
    
    // 初始化字体设置
    driver->font.bold = false;
    driver->font.italic = false;
    driver->font.underline = false;
    driver->font.font_size = 1;
    driver->font.condensed = false;
    driver->font.char_spacing = 0;
    driver->font.line_spacing = 6;      // 6/72英寸
    
    // 初始化打印任务
    driver->job.document_name = strdup("未命名文档");
    driver->job.total_pages = 0;
    driver->job.current_page = 0;
    driver->job.copies = 1;
    driver->job.collate = false;
    
    // 创建打印缓冲区
    driver->buffer_size = 4096;  // 4KB缓冲区
    driver->buffer = (char*)malloc(driver->buffer_size);
    
    if (!driver->buffer) {
        printf("错误: 缓冲区内存分配失败
");
        free(driver->job.document_name);
        free(driver);
        return NULL;
    }
    
    driver->buffer_pos = 0;
    
    printf("打印机驱动已创建
");
    return driver;
}
 
// 释放打印机驱动
void free_printer_driver(PrinterDriver *driver) {
    if (!driver) return;
    
    if (driver->job.document_name) {
        free(driver->job.document_name);
    }
    
    if (driver->buffer) {
        free(driver->buffer);
    }
    
    free(driver);
    printf("打印机驱动已释放
");
}
 
// 初始化8255A
void init_printer_hardware(PrinterDriver *driver) {
    printf("
初始化打印机硬件...
");
    
    // 配置控制字:
    // 位7=1: 模式设定
    // 位6-5=00: 组A工作在模式0
    // 位4=1: 端口A为输出(数据)
    // 位3=0: 端口C上半部分为输入
    // 位2=0: 组B工作在模式0
    // 位1=0: 端口B为输入(状态)
    // 位0=1: 端口C下半部分为输出(控制)
    uint16_t control_port = driver->hw.control_port + 1;  // 控制寄存器
    uint8_t control = 0x80 | 0x10 | 0x01;
    outb(control_port, control);
    
    // 设置初始控制信号
    // STROBE=1, AUTO_FD=1, INIT=1, SELECT_IN=0
    uint8_t ctrl_value = 0x07;  // 0b00000111
    outb(driver->hw.control_port, ctrl_value);
    
    // 清空数据端口
    outb(driver->hw.data_port, 0x00);
    
    printf("8255A初始化完成
");
}
 
// 更新打印机状态
bool update_printer_status(PrinterDriver *driver) {
    printf("
更新打印机状态...
");
    
    uint8_t status = inb(driver->hw.status_port);
    
    // 解析状态寄存器
    driver->status.busy = (status & 0x80) ? true : false;
    driver->status.paper_available = (status & 0x20) ? false : true;
    driver->status.online = (status & 0x10) ? true : false;
    driver->status.error = (status & 0x08) ? false : true;
    
    printf("打印机状态:
");
    printf("  在线: %s
", driver->status.online ? "是" : "否");
    printf("  有纸: %s
", driver->status.paper_available ? "是" : "否");
    printf("  忙: %s
", driver->status.busy ? "是" : "否");
    printf("  错误: %s
", driver->status.error ? "是" : "否");
    
    // 检查是否有错误状态
    if (driver->status.error) {
        driver->status.error_code = 1;  // 通用错误
        printf("错误代码: %d
", driver->status.error_code);
        return false;
    }
    
    if (!driver->status.online) {
        driver->status.error_code = 2;  // 离线错误
        printf("错误代码: %d (打印机离线)
", driver->status.error_code);
        return false;
    }
    
    if (!driver->status.paper_available) {
        driver->status.error_code = 3;  // 缺纸错误
        printf("错误代码: %d (缺纸)
", driver->status.error_code);
        return false;
    }
    
    driver->status.error_code = 0;
    return true;
}
 
// 复位打印机
bool reset_printer(PrinterDriver *driver) {
    printf("
复位打印机...
");
    
    // 读取当前控制端口值
    uint8_t ctrl_value = inb(driver->hw.control_port);
    
    // 生成INIT信号(低电平脉冲)
    ctrl_value &= ~0x04;  // INIT=0
    outb(driver->hw.control_port, ctrl_value);
    
    // 等待足够长的时间
    usleep(100000);  // 100毫秒
    
    // 恢复INIT信号
    ctrl_value |= 0x04;  // INIT=1
    outb(driver->hw.control_port, ctrl_value);
    
    // 等待打印机完成初始化
    printf("等待打印机初始化...
");
    usleep(2000000);  // 2秒
    
    // 检查打印机状态
    if (!update_printer_status(driver)) {
        printf("打印机初始化后状态异常
");
        return false;
    }
    
    printf("打印机复位完成
");
    return true;
}
 
// 向打印机发送一个字节
bool send_byte_to_printer(PrinterDriver *driver, uint8_t byte) {
    // 检查打印机状态
    if (driver->status.busy) {
        printf("打印机忙,等待...
");
        
        // 等待打印机就绪
        int timeout_counter = 0;
        do {
            usleep(10000);  // 等待10毫秒
            update_printer_status(driver);
            timeout_counter++;
            
            if (timeout_counter > 500) {  // 最多等待5秒
                printf("错误: 等待打印机就绪超时
");
                return false;
            }
        } while (driver->status.busy);
    }
    
    // 发送数据到数据端口
    outb(driver->hw.data_port, byte);
    
    // 生成STROBE信号
    uint8_t ctrl_value = inb(driver->hw.control_port);
    
    // STROBE低脉冲
    ctrl_value &= ~0x01;  // STROBE=0
    outb(driver->hw.control_port, ctrl_value);
    
    usleep(1000);  // 等待1毫秒
    
    ctrl_value |= 0x01;  // STROBE=1
    outb(driver->hw.control_port, ctrl_value);
    
    // 更新状态(打印机应该变为忙碌状态)
    update_printer_status(driver);
    
    return true;
}
 
// 设置打印机特性
bool set_printer_property(PrinterDriver *driver, const char *property, const char *value) {
    printf("
设置打印机属性: %s = %s
", property, value);
    
    if (strcmp(property, "auto_linefeed") == 0) {
        if (strcmp(value, "true") == 0 || strcmp(value, "1") == 0) {
            driver->page.auto_linefeed = true;
            
            // 修改硬件自动换行设置
            uint8_t ctrl_value = inb(driver->hw.control_port);
            ctrl_value &= ~0x02;  // AUTO_FD=0 (启用自动换行)
            outb(driver->hw.control_port, ctrl_value);
        } else {
            driver->page.auto_linefeed = false;
            
            // 修改硬件自动换行设置
            uint8_t ctrl_value = inb(driver->hw.control_port);
            ctrl_value |= 0x02;  // AUTO_FD=1 (禁用自动换行)
            outb(driver->hw.control_port, ctrl_value);
        }
    } else if (strcmp(property, "left_margin") == 0) {
        driver->page.left_margin = atoi(value);
    } else if (strcmp(property, "top_margin") == 0) {
        driver->page.top_margin = atoi(value);
    } else if (strcmp(property, "page_width") == 0) {
        driver->page.page_width = atoi(value);
    } else if (strcmp(property, "page_length") == 0) {
        driver->page.page_length = atoi(value);
    } else if (strcmp(property, "bold") == 0) {
        driver->font.bold = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
    } else if (strcmp(property, "italic") == 0) {
        driver->font.italic = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
    } else if (strcmp(property, "underline") == 0) {
        driver->font.underline = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
    } else if (strcmp(property, "font_size") == 0) {
        driver->font.font_size = atoi(value);
    } else if (strcmp(property, "copies") == 0) {
        driver->job.copies = atoi(value);
    } else {
        printf("未知的属性: %s
", property);
        return false;
    }
    
    printf("属性设置成功
");
    return true;
}
 
// 发送命令到打印机
bool send_command_to_printer(PrinterDriver *driver, uint8_t command, uint8_t *params, int param_count) {
    printf("
发送命令到打印机: 0x%02X
", command);
    
    // 发送命令字节
    if (!send_byte_to_printer(driver, command)) {
        return false;
    }
    
    // 发送参数(如果有)
    for (int i = 0; i < param_count; i++) {
        if (!send_byte_to_printer(driver, params[i])) {
            return false;
        }
    }
    
    return true;
}
 
// 发送ESC/P命令序列
bool send_esc_sequence(PrinterDriver *driver, uint8_t command, uint8_t *params, int param_count) {
    // 首先发送ESC
    if (!send_byte_to_printer(driver, CMD_ESC)) {
        return false;
    }
    
    // 然后发送命令和参数
    return send_command_to_printer(driver, command, params, param_count);
}
 
// 添加文本到缓冲区
bool add_text_to_buffer(PrinterDriver *driver, const char *text) {
    printf("
添加文本到缓冲区: "%s"
", text);
    
    size_t text_len = strlen(text);
    
    // 检查缓冲区是否有足够空间
    if (driver->buffer_pos + text_len >= driver->buffer_size) {
        // 缓冲区已满,需要刷新
        printf("缓冲区已满,刷新缓冲区
");
        
        // 这里应该实现缓冲区刷新
        // 简化起见,我们只是清空缓冲区
        driver->buffer_pos = 0;
    }
    
    // 复制文本到缓冲区
    memcpy(driver->buffer + driver->buffer_pos, text, text_len);
    driver->buffer_pos += text_len;
    
    printf("文本已添加到缓冲区,当前缓冲区使用: %zu/%zu
", 
           driver->buffer_pos, driver->buffer_size);
           
    return true;
}
 
// 应用字体设置
bool apply_font_settings(PrinterDriver *driver) {
    printf("
应用字体设置...
");
    
    // 设置粗体
    if (driver->font.bold) {
        uint8_t params[] = {'E'};  // ESC E - 启用粗体
        if (!send_esc_sequence(driver, 'E', NULL, 0)) {
            return false;
        }
    } else {
        uint8_t params[] = {'F'};  // ESC F - 禁用粗体
        if (!send_esc_sequence(driver, 'F', NULL, 0)) {
            return false;
        }
    }
    
    // 设置斜体
    if (driver->font.italic) {
        uint8_t params[] = {'4'};  // ESC 4 - 启用斜体
        if (!send_esc_sequence(driver, '4', NULL, 0)) {
            return false;
        }
    } else {
        uint8_t params[] = {'5'};  // ESC 5 - 禁用斜体
        if (!send_esc_sequence(driver, '5', NULL, 0)) {
            return false;
        }
    }
    
    // 设置下划线
    if (driver->font.underline) {
        uint8_t params[] = {'-', 1};  // ESC - 1 - 启用下划线
        if (!send_esc_sequence(driver, '-', &params[1], 1)) {
            return false;
        }
    } else {
        uint8_t params[] = {'-', 0};  // ESC - 0 - 禁用下划线
        if (!send_esc_sequence(driver, '-', &params[1], 1)) {
            return false;
        }
    }
    
    // 设置字体大小
    if (driver->font.font_size > 1) {
        // 放大字体
        uint8_t size_param = driver->font.font_size - 1;
        uint8_t params[] = {'w', size_param};  // ESC w n - 设置字体大小
        if (!send_esc_sequence(driver, 'w', &params[1], 1)) {
            return false;
        }
    }
    
    printf("字体设置已应用
");
    return true;
}
 
// 应用页面设置
bool apply_page_settings(PrinterDriver *driver) {
    printf("
应用页面设置...
");
    
    // 设置页面长度
    uint8_t params[] = {'C', driver->page.page_length};  // ESC C n - 设置页长
    if (!send_esc_sequence(driver, 'C', &params[1], 1)) {
        return false;
    }
    
    // 设置左边距
    uint8_t margin_params[] = {'l', driver->page.left_margin & 0xFF, driver->page.left_margin >> 8};  // ESC l n1 n2 - 设置左边距
    if (!send_esc_sequence(driver, 'l', &margin_params[1], 2)) {
        return false;
    }
    
    // 设置行间距
    uint8_t line_space_params[] = {'A', driver->font.line_spacing};  // ESC A n - 设置行间距
    if (!send_esc_sequence(driver, 'A', &line_space_params[1], 1)) {
        return false;
    }
    
    printf("页面设置已应用
");
    return true;
}
 
// 打印缓冲区内容
bool print_buffer(PrinterDriver *driver) {
    printf("
打印缓冲区内容...
");
    
    // 检查缓冲区是否为空
    if (driver->buffer_pos == 0) {
        printf("缓冲区为空,没有内容可打印
");
        return true;
    }
    
    // 应用字体和页面设置
    if (!apply_font_settings(driver)) {
        return false;
    }
    
    if (!apply_page_settings(driver)) {
        return false;
    }
    
    // 逐字节发送缓冲区内容
    printf("发送 %zu 字节到打印机...
", driver->buffer_pos);
    
    for (size_t i = 0; i < driver->buffer_pos; i++) {
        if (!send_byte_to_printer(driver, (uint8_t)driver->buffer[i])) {
            printf("发送字节 %zu 失败
", i);
            return false;
        }
        
        // 小批量发送,模拟实际打印过程
        if (i % 16 == 15) {
            usleep(10000);  // 每16个字节暂停10毫秒
        }
    }
    
    // 发送一个换页命令
    if (!send_byte_to_printer(driver, CMD_FF)) {
        return false;
    }
    
    // 清空缓冲区
    driver->buffer_pos = 0;
    
    printf("缓冲区内容已打印并清空
");
    return true;
}
 
// 设置打印任务信息
void set_print_job_info(PrinterDriver *driver, const char *doc_name, int total_pages) {
    printf("
设置打印任务信息...
");
    
    // 释放旧的文档名称
    if (driver->job.document_name) {
        free(driver->job.document_name);
    }
    
    // 设置新的任务信息
    driver->job.document_name = strdup(doc_name);
    driver->job.total_pages = total_pages;
    driver->job.current_page = 0;
    
    printf("打印任务信息已设置:
");
    printf("  文档名称: %s
", driver->job.document_name);
    printf("  总页数: %d
", driver->job.total_pages);
    printf("  份数: %d
", driver->job.copies);
    printf("  逐份打印: %s
", driver->job.collate ? "是" : "否");
}
 
// 打印示例文档
bool print_sample_document(PrinterDriver *driver) {
    printf("
打印示例文档...
");
    
    // 设置任务信息
    set_print_job_info(driver, "示例文档", 1);
    
    // 重置打印机
    if (!reset_printer(driver)) {
        return false;
    }
    
    // 添加标题文本
    add_text_to_buffer(driver, "

8255A打印机驱动程序示例文档
");
    add_text_to_buffer(driver, "============================

");
    
    // 添加正文
    add_text_to_buffer(driver, "这是一个使用8255A接口芯片实现的简易打印机驱动程序演示。

");
    add_text_to_buffer(driver, "它支持以下功能:
");
    add_text_to_buffer(driver, " - 基本文本打印
");
    add_text_to_buffer(driver, " - 字体样式设置(粗体、斜体、下划线)
");
    add_text_to_buffer(driver, " - 页面布局控制
");
    add_text_to_buffer(driver, " - 打印机状态监测
");
    add_text_to_buffer(driver, " - 错误处理

");
    
    add_text_to_buffer(driver, "虽然现代系统已经较少使用并行接口打印机,但了解这些基本原理有助于
");
    add_text_to_buffer(driver, "理解计算机与外设的通信方式以及驱动程序的工作原理。

");
    
    add_text_to_buffer(driver, "8255A是一款经典的可编程并行接口芯片,除了打印机接口外,它还被
");
    add_text_to_buffer(driver, "广泛应用于各种嵌入式系统和工业控制场景。

");
    
    // 打印内容
    driver->job.current_page = 1;
    printf("打印第 %d 页,共 %d 页
", 
           driver->job.current_page, driver->job.total_pages);
    
    if (!print_buffer(driver)) {
        return false;
    }
    
    printf("示例文档打印完成
");
    return true;
}
 
int main() {
    printf("8255A打印机驱动程序示例
");
    printf("=====================
");
    
    // 创建打印机驱动
    PrinterDriver *driver = create_printer_driver();
    
    if (!driver) {
        return 1;
    }
    
    // 初始化打印机硬件
    init_printer_hardware(driver);
    
    // 更新打印机状态
    update_printer_status(driver);
    
    // 检查打印机是否就绪
    if (driver->status.online && driver->status.paper_available && !driver->status.error) {
        // 设置打印机属性
        set_printer_property(driver, "auto_linefeed", "true");
        set_printer_property(driver, "bold", "true");
        set_printer_property(driver, "left_margin", "5");
        
        // 打印示例文档
        print_sample_document(driver);
    } else {
        printf("打印机未就绪,请检查状态
");
    }
    
    // 释放资源
    free_printer_driver(driver);
    
    return 0;
}

这段代码展示了一个相对完整的打印机驱动程序设计,涵盖了打印机驱动程序的核心功能:

硬件抽象:通过结构体封装硬件接口,使上层代码不直接依赖于特定的硬件状态管理:实时监测并维护打印机状态信息命令处理:实现ESC/P打印命令序列的发送缓冲机制:使用缓冲区管理打印数据,提高效率属性配置:支持各种打印属性的设置,如字体、页面布局等任务管理:管理打印任务信息,包括文档名称、页数等错误处理:检测并处理各种打印机错误情况

虽然这个示例主要针对基于8255A的并行打印机,但其设计思想和架构原则适用于各种打印机驱动程序。现代打印机驱动程序虽然接口和协议更复杂,但基本功能和结构仍然类似。

练习题11

解释8255A的三种工作模式的主要特点及应用场合。

在使用8255A实现打印机接口时,STROBE信号的作用是什么?如何正确产生这一信号?

在键盘矩阵扫描中,为什么需要进行去抖动处理?请简要描述一种去抖动方法。

使用8255A实现4位LED数码管显示,如何通过动态扫描技术显示一个四位数字?请给出主要算法流程。

当使用8255A的模式1进行打印机接口设计时,与模式0相比有哪些优势?

在PC/XT系统中,8255A主要用于控制哪些系统功能?这些功能如何通过8255A的不同端口实现?

设计一个使用8255A实现的简单数据采集系统,要求能够从两个模拟传感器读取数据,并在LED显示器上显示。请给出设计框图和关键代码段。

在并行通信接口中,如何使用8255A的模式2实现可靠的双向数据传输?

编写一个C语言函数,使用8255A接口控制一个16键键盘矩阵,要求能检测到多个按键同时按下的情况。

分析并行打印机接口中可能出现的错误情况,并说明驱动程序应如何处理这些错误。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
荭的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容