Content is user-generated and unverified.

ao486 MiSTer Dual Mouse Implementation Guide

Overview

This guide provides implementation details for adding second mouse support to the ao486 MiSTer core. Unlike the Amiga which used two identical mouse ports, DOS PCs typically used PS/2 for the primary mouse and a serial (COM) port for the secondary mouse. This was the configuration used by games like The Settlers and The Settlers II.

Background

Based on the research:

  • The ao486 core already has PS/2 mouse support for the primary mouse
  • The core has COM port (UART) support on COM2 via USER I/O
  • DOS games that support dual mice expect: PS/2 mouse on primary port, Serial mouse on COM1 or COM2
  • Serial mice typically operate at 1200 baud using Microsoft or Mouse Systems protocol

Implementation Strategy

We'll implement dual mouse support by:

  1. Enhancing the MiSTer Main framework to support multiple mice (shared with Minimig implementation)
  2. Adding a serial mouse emulator in the ao486 core
  3. Routing the second USB mouse through the serial port interface
  4. Supporting both Microsoft and Mouse Systems serial mouse protocols

Part 1: Serial Mouse Protocol Implementation

1.1 Create serial_mouse.v

verilog
// serial_mouse.v - Serial mouse emulator for ao486
module serial_mouse (
    input         clk,
    input         reset,
    
    // Mouse input from HPS
    input  [15:0] mouse_x,
    input  [15:0] mouse_y,
    input   [7:0] mouse_buttons,
    input         mouse_strobe,
    
    // Configuration
    input   [1:0] protocol,     // 0=Microsoft, 1=Mouse Systems, 2=Auto
    input   [2:0] baud_select,  // 0=1200, 1=2400, 2=4800, 3=9600
    input         enabled,
    
    // Serial port signals
    output        txd,
    input         rxd,
    output        rts,
    input         cts,
    output        dtr,
    input         dsr
);

// Baud rate generator
reg [15:0] baud_counter;
reg        baud_tick;
wire [15:0] baud_divisor;

// Baud rate selection
assign baud_divisor = (baud_select == 3'd0) ? 16'd2500 :  // 1200 baud @ 30MHz
                      (baud_select == 3'd1) ? 16'd1250 :  // 2400 baud
                      (baud_select == 3'd2) ? 16'd625  :  // 4800 baud
                                              16'd313;     // 9600 baud

always @(posedge clk) begin
    if (reset) begin
        baud_counter <= 16'd0;
        baud_tick <= 1'b0;
    end else begin
        if (baud_counter >= baud_divisor) begin
            baud_counter <= 16'd0;
            baud_tick <= 1'b1;
        end else begin
            baud_counter <= baud_counter + 16'd1;
            baud_tick <= 1'b0;
        end
    end
end

// Mouse state tracking
reg [15:0] last_x, last_y;
reg  [7:0] last_buttons;
reg        strobe_d;
reg signed [7:0] delta_x, delta_y;
reg        data_ready;

// Detect mouse movement
always @(posedge clk) begin
    if (reset) begin
        last_x <= 16'd0;
        last_y <= 16'd0;
        last_buttons <= 8'hFF;
        strobe_d <= 1'b0;
        delta_x <= 8'd0;
        delta_y <= 8'd0;
        data_ready <= 1'b0;
    end else begin
        strobe_d <= mouse_strobe;
        
        if (mouse_strobe != strobe_d && enabled) begin
            // Calculate deltas (clamp to -128 to +127)
            delta_x <= (mouse_x - last_x > 127) ? 8'd127 :
                       (mouse_x - last_x < -128) ? -8'd128 :
                       mouse_x - last_x;
            delta_y <= (mouse_y - last_y > 127) ? 8'd127 :
                       (mouse_y - last_y < -128) ? -8'd128 :
                       mouse_y - last_y;
            
            last_x <= mouse_x;
            last_y <= mouse_y;
            last_buttons <= mouse_buttons;
            
            // Signal new data available
            if ((mouse_x != last_x) || (mouse_y != last_y) || 
                (mouse_buttons != last_buttons)) begin
                data_ready <= 1'b1;
            end
        end
    end
end

// Microsoft Mouse Protocol State Machine
// Format: 1LRY5Y4Y3Y2 1X5X4X3X2 0Y1Y0X1X0 (3 bytes)
// L=Left button, R=Right button
reg [2:0] ms_state;
reg [7:0] ms_packet[0:2];
reg       ms_sending;

always @(posedge clk) begin
    if (reset) begin
        ms_state <= 3'd0;
        ms_sending <= 1'b0;
    end else if (protocol == 2'd0 && data_ready && !ms_sending) begin
        // Prepare Microsoft protocol packet
        ms_packet[0] <= {2'b01,            // Sync bits
                         ~last_buttons[0], // Left button (active low)
                         ~last_buttons[1], // Right button
                         delta_y[5:2]};    // Y movement high bits
        ms_packet[1] <= {2'b00,            // Sync bits
                         delta_x[5:0]};    // X movement
        ms_packet[2] <= {2'b00,            // Sync bits
                         delta_y[1:0],     // Y movement low bits
                         delta_x[1:0]};    // X movement low bits (unused)
        ms_sending <= 1'b1;
        data_ready <= 1'b0;
    end
end

// UART Transmitter
reg [3:0] tx_state;
reg [7:0] tx_byte;
reg [2:0] tx_bit;
reg       tx_busy;

assign txd = (tx_state == 4'd0) ? 1'b1 :        // Idle
             (tx_state == 4'd1) ? 1'b0 :        // Start bit
             (tx_state >= 4'd2 && tx_state <= 4'd9) ? tx_byte[tx_bit] : // Data bits
             1'b1;                               // Stop bit

always @(posedge clk) begin
    if (reset) begin
        tx_state <= 4'd0;
        tx_busy <= 1'b0;
        ms_state <= 3'd0;
    end else if (baud_tick) begin
        case (tx_state)
            4'd0: begin  // Idle
                if (ms_sending && ms_state < 3'd3) begin
                    tx_byte <= ms_packet[ms_state];
                    tx_state <= 4'd1;
                    tx_busy <= 1'b1;
                    tx_bit <= 3'd0;
                end
            end
            
            4'd1: begin  // Start bit
                tx_state <= 4'd2;
            end
            
            4'd2, 4'd3, 4'd4, 4'd5, 
            4'd6, 4'd7, 4'd8, 4'd9: begin  // Data bits
                tx_bit <= tx_bit + 3'd1;
                tx_state <= tx_state + 4'd1;
            end
            
            4'd10: begin  // Stop bit
                tx_state <= 4'd0;
                tx_busy <= 1'b0;
                ms_state <= ms_state + 3'd1;
                if (ms_state >= 3'd2) begin
                    ms_sending <= 1'b0;
                    ms_state <= 3'd0;
                end
            end
        endcase
    end
end

// Control signals
assign rts = 1'b1;  // Always ready to send
assign dtr = 1'b1;  // Always ready

endmodule

1.2 Integrate Serial Mouse into ao486

Modify the ao486 SoC to include the serial mouse emulator:

verilog
// In ao486.sv - Add serial mouse instance

// Second mouse signals from HPS
wire [15:0] mouse1_x, mouse1_y;
wire  [7:0] mouse1_buttons;
wire        mouse1_strobe;

// Serial mouse configuration
reg   [1:0] serial_mouse_protocol = 2'd0;  // Microsoft protocol
reg   [2:0] serial_mouse_baud = 3'd0;      // 1200 baud
reg         serial_mouse_enabled = 1'b0;

// COM2 signals (internal)
wire com2_txd_mouse;
wire com2_rxd;
wire com2_rts_mouse;
wire com2_cts;
wire com2_dtr_mouse;
wire com2_dsr;

// Instantiate serial mouse emulator
serial_mouse serial_mouse_inst (
    .clk(clk_sys),
    .reset(reset),
    
    // Mouse input
    .mouse_x(mouse1_x),
    .mouse_y(mouse1_y),
    .mouse_buttons(mouse1_buttons),
    .mouse_strobe(mouse1_strobe),
    
    // Configuration
    .protocol(serial_mouse_protocol),
    .baud_select(serial_mouse_baud),
    .enabled(serial_mouse_enabled),
    
    // Serial port
    .txd(com2_txd_mouse),
    .rxd(com2_rxd),
    .rts(com2_rts_mouse),
    .cts(com2_cts),
    .dtr(com2_dtr_mouse),
    .dsr(com2_dsr)
);

// Mux COM2 between UART and serial mouse
wire com2_txd = serial_mouse_enabled ? com2_txd_mouse : uart_txd;
wire com2_rts = serial_mouse_enabled ? com2_rts_mouse : uart_rts;
wire com2_dtr = serial_mouse_enabled ? com2_dtr_mouse : uart_dtr;

// Add to OSD configuration
localparam CONF_STR = {
    // ... existing config ...
    "O[35],Serial Mouse,Disabled,Enabled;",
    "O[37:36],Serial Mouse Protocol,Microsoft,Mouse Systems,Auto;",
    "O[40:38],Serial Mouse Baud,1200,2400,4800,9600;",
    // ... rest of config ...
};

// Process configuration
always @(posedge clk_sys) begin
    serial_mouse_enabled <= status[35];
    serial_mouse_protocol <= status[37:36];
    serial_mouse_baud <= status[40:38];
end

Part 2: DOS Software Configuration

2.1 Mouse Driver Setup

For DOS to recognize both mice, you need appropriate drivers:

batch
REM AUTOEXEC.BAT - Load both mouse drivers
REM Load PS/2 mouse driver first
C:\MOUSE\CTMOUSE.EXE /P

REM Load serial mouse driver on COM2
C:\MOUSE\SERMOUSE.EXE COM2

2.2 Game-Specific Configuration

The Settlers

ini
; SETTLERS.CFG
[MOUSE]
PLAYER1=PS2
PLAYER2=COM2

The Settlers II

The game auto-detects serial mice on COM ports during startup.

Part 3: Enhanced HPS Communication

3.1 Update hps_io.v for Second Mouse

This is shared with the Minimig implementation but adapted for ao486:

verilog
// In hps_io.v - Add second mouse support
module hps_io_ao486 #(parameter STRLEN=0, PS2DIV=2000)
(
    input             clk_sys,
    inout      [48:0] HPS_BUS,
    
    // Primary PS/2 mouse (existing)
    output reg  [2:0] ps2_mouse,
    output reg [24:0] ps2_mouse_data,
    
    // Secondary mouse for serial emulation
    output reg [15:0] mouse1_x,
    output reg [15:0] mouse1_y,
    output reg  [7:0] mouse1_buttons,
    output reg        mouse1_strobe,
    
    // ... other signals ...
);

// Process extended mouse command
always @(posedge clk_sys) begin
    if (cmd == UIO_MOUSE_EXT && data_cnt == 6) begin
        if (data_buf[7:0] == 8'd1) begin  // Second mouse
            mouse1_x <= {data_buf[23:16], data_buf[15:8]};
            mouse1_y <= {data_buf[39:32], data_buf[31:24]};
            mouse1_buttons <= data_buf[47:40];
            mouse1_strobe <= ~mouse1_strobe;
        end
        data_cnt <= 0;
    end
end

endmodule

Part 4: Testing Implementation

4.1 DOS Test Program

Create a test program to verify both mice are working:

c
// DUALMOUSE.C - Test dual mouse support
#include <dos.h>
#include <stdio.h>
#include <conio.h>

// Mouse interrupt functions
#define MOUSE_INT    0x33
#define RESET_MOUSE  0x00
#define SHOW_CURSOR  0x01
#define GET_STATUS   0x03

// Serial mouse functions
#define COM2_BASE    0x2F8

typedef struct {
    int x, y;
    int buttons;
    int present;
} MouseInfo;

MouseInfo ps2_mouse = {0};
MouseInfo ser_mouse = {0};

// Check for PS/2 mouse
int check_ps2_mouse() {
    union REGS regs;
    regs.x.ax = RESET_MOUSE;
    int86(MOUSE_INT, &regs, &regs);
    return (regs.x.ax == 0xFFFF);
}

// Initialize serial mouse on COM2
int init_serial_mouse() {
    // Set COM2 to 1200,N,8,1
    outportb(COM2_BASE + 3, 0x80);  // Enable DLAB
    outportb(COM2_BASE + 0, 0x60);  // Divisor low (1200 baud)
    outportb(COM2_BASE + 1, 0x00);  // Divisor high
    outportb(COM2_BASE + 3, 0x03);  // 8N1, disable DLAB
    outportb(COM2_BASE + 4, 0x0B);  // DTR, RTS, OUT2
    
    // Check if mouse responds
    delay(100);
    if (inportb(COM2_BASE + 5) & 0x01) {
        return 1;  // Data available = mouse present
    }
    return 0;
}

// Read PS/2 mouse
void read_ps2_mouse() {
    union REGS regs;
    regs.x.ax = GET_STATUS;
    int86(MOUSE_INT, &regs, &regs);
    ps2_mouse.x = regs.x.cx;
    ps2_mouse.y = regs.x.dx;
    ps2_mouse.buttons = regs.x.bx;
}

// Read serial mouse (Microsoft protocol)
void read_serial_mouse() {
    static unsigned char buffer[3];
    static int byte_count = 0;
    
    while (inportb(COM2_BASE + 5) & 0x01) {  // Data available
        unsigned char byte = inportb(COM2_BASE);
        
        // Sync on first byte (bit 6 set)
        if (byte_count == 0 && !(byte & 0x40)) continue;
        
        buffer[byte_count++] = byte;
        
        if (byte_count == 3) {
            // Decode Microsoft mouse packet
            ser_mouse.buttons = ((buffer[0] >> 5) & 0x03) ^ 0x03;
            ser_mouse.x += (char)((buffer[0] << 6) | (buffer[1] & 0x3F));
            ser_mouse.y += (char)((buffer[0] << 4) | (buffer[2] & 0x3F));
            byte_count = 0;
        }
    }
}

int main() {
    clrscr();
    printf("Dual Mouse Test for ao486\n");
    printf("=========================\n\n");
    
    // Check PS/2 mouse
    ps2_mouse.present = check_ps2_mouse();
    printf("PS/2 Mouse: %s\n", ps2_mouse.present ? "Found" : "Not found");
    
    // Check serial mouse
    ser_mouse.present = init_serial_mouse();
    printf("Serial Mouse on COM2: %s\n", ser_mouse.present ? "Found" : "Not found");
    
    if (!ps2_mouse.present && !ser_mouse.present) {
        printf("\nNo mice detected!\n");
        return 1;
    }
    
    printf("\nPress ESC to exit\n\n");
    
    // Show mouse cursor
    if (ps2_mouse.present) {
        union REGS regs;
        regs.x.ax = SHOW_CURSOR;
        int86(MOUSE_INT, &regs, &regs);
    }
    
    // Main loop
    while (!kbhit() || getch() != 27) {
        if (ps2_mouse.present) read_ps2_mouse();
        if (ser_mouse.present) read_serial_mouse();
        
        gotoxy(1, 10);
        printf("PS/2 Mouse  - X:%4d Y:%4d Buttons:%02X\n", 
               ps2_mouse.x, ps2_mouse.y, ps2_mouse.buttons);
        printf("Serial Mouse - X:%4d Y:%4d Buttons:%02X\n", 
               ser_mouse.x, ser_mouse.y, ser_mouse.buttons);
    }
    
    return 0;
}

4.2 Compile and Run

batch
REM Compile with Turbo C
TCC -mc DUALMOUSE.C

REM Run the test
DUALMOUSE.EXE

Part 5: Alternative Implementation - UART Passthrough

An alternative approach is to use the existing UART support with a USB-to-Serial adapter:

5.1 Hardware Setup

  1. Connect a USB-to-Serial adapter (FT232-based) to MiSTer
  2. Connect a real serial mouse or use a Raspberry Pi as serial mouse emulator

5.2 Software Configuration

batch
REM In MiSTer OSD
UART: MidiLink/Remote/Serial

REM In DOS
MODE COM2:1200,N,8,1

Part 6: Main_MiSTer Integration

The Main_MiSTer changes are shared with the Minimig implementation (see the Minimig guide), but we need to ensure the ao486 core is included in the multi-mouse support:

cpp
// In input_mouse.cpp - Add ao486 core detection
bool is_ao486_core() {
    char corename[64];
    user_io_get_core_name(corename, sizeof(corename));
    return (strcasecmp(corename, "ao486") == 0);
}

// In mouse event handler
void handle_mouse_events() {
    // ... existing code ...
    
    if (is_minimig_core() || is_ao486_core()) {
        // Enable multi-mouse support for these cores
        send_extended_mouse_data();
    } else {
        // Use legacy single mouse for other cores
        send_legacy_mouse_data();
    }
}

Part 7: Performance Considerations

7.1 Timing Issues

The ao486 core has known timing sensitivities. The serial mouse emulation must:

  • Generate accurate baud rates
  • Maintain proper inter-byte timing
  • Handle the PS2DIV timing correctly (avoid mouse jumping issues)

7.2 CPU Speed Adaptation

verilog
// Adjust serial timing based on CPU speed
wire [15:0] baud_adjust = (cpu_speed == 2'd0) ? 16'd1 :    // 15MHz
                          (cpu_speed == 2'd1) ? 16'd2 :    // 33MHz
                          (cpu_speed == 2'd2) ? 16'd3 :    // 56MHz
                                                16'd3;      // 90MHz

wire [15:0] adjusted_divisor = baud_divisor * baud_adjust;

Conclusion

This implementation provides dual mouse support for the ao486 core, enabling games like The Settlers to use two mice simultaneously. The approach:

  1. Emulates a serial mouse on COM2 for the second USB mouse
  2. Maintains compatibility with existing PS/2 mouse support
  3. Supports both Microsoft and Mouse Systems protocols
  4. Integrates with the shared Main_MiSTer multi-mouse framework

Key differences from Minimig implementation:

  • Uses serial protocol instead of quadrature encoding
  • Requires DOS driver support
  • More complex protocol implementation
  • Limited to specific games that support serial mice

The modular design allows for future enhancements like:

  • COM1 support for different games
  • Additional serial mouse protocols
  • Integration with real serial mice via USER I/O
Content is user-generated and unverified.
    ao486 MiSTer Dual Mouse Implementation Guide | Claude