Adding PS/2 mouse to my FPGA computer

This is a followup of my original post.

So far I had PS/keyboard only on my FPGA computer. The time has come to add the mouse, too. Without any investigation how PS/2 mouse works, I first tried to plug the mouse into my PS/2 keyboard connector and watch what would come from it. It didn't work. The keyboard worked, but the mouse didn't. I expected that the mouse would send bytes as I move or click, but it didn't. After a brief investigation, I found out that the PS/2 mouse needs an initialization in order to start sending bytes to the computer.

PS/2 is actually a bidirectional interface. Both computer and mouse/keyboard can send bytes to the other. Well, the initialization sequence for the mouse actually means that the computer needs to send one byte to the mouse. Unfortunately, that is not so simple. In order to send a command to the mouse, host (computer) needs to set both data and clock lines low for a given period of time, then to release both lines, and then to start setting bits of the command in synchronization with the clock that has just started to arrive from the mouse.

Fortunately, there is a module that already does all these steps, and can be found here.

I have replaced my original PS2 module with this one and now I have two ports in my computer:

// ####################################
// PS/2 keyboard instance
// ####################################
wire [7:0] ps2_data;
wire ps2_received;
reg [7:0] ps2_data_r;
PS2_Controller #(.INITIALIZE_MOUSE(0)) PS2 (
    // Inputs
    .CLOCK_50           (CLOCK_50),
    .reset              (~KEY[0]),
    // Bidirectionals
    .PS2_CLK            (gpio0[33]),
    .PS2_DAT            (gpio0[31]),
    // Outputs
    .received_data      (ps2_data),
    .received_data_en   (ps2_received)
); 
// ####################################
// PS/2 mouse instance
// ####################################
wire [7:0] ps2_data_mouse;
wire ps2_received_mouse;
reg [7:0] ps2_data_r_mouse;
PS2_Controller PS2_mouse (
    // Inputs
    .CLOCK_50           (CLOCK_50),
    .reset              (~KEY[0]),
    // Bidirectionals
    .PS2_CLK            (gpio0[2]),
    .PS2_DAT            (gpio0[4]),
    // Outputs
    .received_data      (ps2_data_mouse),
    .received_data_en   (ps2_received_mouse)
); 

The default value for the INITIALIZE_MOUSE parameter is 1, so the mouse controller initializes the mouse at reset.

I have allocated another IRQ for the mouse: 

localparam IRQ_PS2_MOUSE   = 5;

In the main irq loop, the mouse actually triggers the CPU interrupt #5:

always @ (posedge clk100) begin
    ...
    // ############################### IRQ2 - PS/2 keyboard #############################
    if (ps2_received) begin
        ps2_data_r <= ps2_data;
        // if we have received a byte from the keyboard, we will trigger the IRQ#2
        irq[IRQ_PS2] <= 1'b1;
    end
    else 
    begin
        irq[IRQ_PS2] <= 1'b0;
    end
    // ############################### IRQ5 - PS/2 mouse #############################
    if (ps2_received_mouse) begin
        ps2_data_r_mouse <= ps2_data_mouse;
        // if we have received a byte from the keyboard, we will trigger the IRQ#2
        irq[IRQ_PS2_MOUSE] <= 1'b1;
    end
    else 
    begin
        irq[IRQ_PS2_MOUSE] <= 1'b0;
    end
    ...
end

When the CPU detects that some bit in the irq register is set to 1, it triggers the interrupt handler routine. It does that by first checking if the interrupt vector is not zero. After that, the CPU pushes the current PC register and flags to the stack and then jumps to the interrupt handling routine. 

The mouse interrupt routine receives three bytes from the PS/2 mouse:

unsigned short int *PORT_PS2_MOUSE  = (unsigned short int *)(0x80000000 + 800)  ; // port for PS2 mouse

void ps2_mouse_irq_triggered()
{
asm 
    (
        "push r0\npush r1\npush r2\npush r3\npush r4\npush r5\npush r6\npush r7\npush r8\npush r9\npush r10\npush r11\npush r12\npush r13\n"
    );

    mouse_byte[mouse_counter++] = *PORT_PS2_MOUSE;
    if (mouse_counter == 3)
        mouse_counter = 0;

    asm 
    (
        "pop r13\npop r12\npop r11\npop r10\npop r9\npop r8\npop r7\npop r6\npop r5\npop r4\npop r3\npop r2\npop r1\npop r0\nmov.w sp,r13\npop r13\niret"
    );
}

void init_mouse() {
    mouse_counter = 0;
    *PS2_MOUSE_HANDLER_INSTR    = 1;
    *PS2_MOUSE_HANDLER_ADDR     = (int)&ps2_mouse_irq_triggered;
}

Three bytes for each mouse event come one by one. When all three bytes arrive, we are ready to process them in the main program:

        if ((mouse_counter == 0) && (
            mouse_byte[0] != old_mouse_byte[0] ||
            mouse_byte[1] != old_mouse_byte[1] || 
            mouse_byte[2] != old_mouse_byte[2])) {
                sprintf(str"mouse: %d, %d, %d"mouse_byte[0], mouse_byte[1], mouse_byte[2]);
                draw(1020REDstr);
                
                old_mouse_byte[0] = mouse_byte[0];
                old_mouse_byte[1] = mouse_byte[1];
                old_mouse_byte[2] = mouse_byte[2];
                ...

The first byte gives the button status: which button has been pressed. The second byte gives the x-axis speed of movement, or the amount of pixels the mouse has moved, while the third byte does the same, just for the y-axis. Both second and third byte are 8-bit signed values, meaning that if you read a value greater than 127, the value is negative (and can be calculated by subtracting it from the 256).

The whole demo can be seen here:



Conclusion

Adding PS/2 mouse was not that complicated once I managed to find the proper Verilog controller. After that, it was just the matter of allocating one more interrupt and writing handlers for it.


Comments

Comments powered by Disqus