USB to quadrature encoder so you use modern USB mice on the Amiga.

Dependencies:   USBHOST

Introduction

This project is devised so you can attach a mouse (and/or steam controller) to a classic computer such as an Amiga. Computers of that era use bus mice which just send the pulses from the quadrature encoder wheels on the mouse directly to the computer with out interpretation.

Use

Program a build base on this repository to a STM32 nucleo board (I tested this on a NUCLEO STM32F411 board). You will then need connect the Nucleo board to your computers joystick and mouse ports as follows (if you only use a mouse you won't need a joystick lead :) ). If you use a steam controller the thumb stick and left pad are mapped to the Amiga's joystick port and the right pad and front trigger buttons are mapped to the mouse.

mouse_port_forward(D10) - Amiga Joystick pin 1
mouse_port_back(D11) - Amiga Joystick pin 2
mouse_port_left(D12) - Amiga Joystick pin 3
mouse_port_right(D13) - Amiga Joystick pin 4
mouse_port_left_button(D9) - Amiga Joystick pin 6
GND - Amiga joystick pin 8
mouse_port_right_button(D8) - Amiga Joystick pin 9

game_port_forward(PC_8) - Amiga Joystick pin 1
game_port_back(PC_12) - Amiga Joystick pin 2
game_port_left(PC_10) - Amiga Joystick pin 3
game_port_right(PC_11) - Amiga Joystick pin 4
game_port_left_button(PC_6) - Amiga Joystick pin 6
GND - Amiga joystick pin 8
game_port_right_button(PC_5) - Amiga Joystick pin 9

It is best NOT to use the 5V pin from the Amiga's joystick port to power the nucleo board as the power suppled by the Amiga is limited!

You will also need to attach a USB socket to the nuclea board so you can use the Nucleo's usb OTG support.

(1) 5V     - 5v
(2) PA11 - Data-
(3) PA12 - Data+
(4) GND - GND

According to the usb spec it is required to attach a 15k ohm resistor between D-/+ and ground but it appears the USB host works fine without this.

NOTES:

  • The right touch pad can take a little while before it generates mouse events.
  • I found that the STM USBHost library needed some modifications to correctly work with boot protocol (it needs to call set protocol) and hubs (getSize doesn't report the correct size). This project uses a modified USB host library with fixes for this (they will need a little more work before I can create a pull request).
Revision:
4:02e4f0ab23ff
Parent:
3:24067f6310fa
--- a/main.cpp	Mon Aug 21 22:01:47 2017 +0000
+++ b/main.cpp	Sat Aug 26 10:14:24 2017 +0000
@@ -6,23 +6,39 @@
 #include "FATFileSystem.h"
 #include <stdlib.h>
 
-int x_cur = 0;   
-int y_cur = 0;
-int x_pos = 0;
-int y_pos = 0;
+inline int max ( int a, int b ) { return a > b ? a : b; }
+
+#define MAX_QUADRATURE_FREQ 3500
+
+volatile int x_cur = 0;   
+volatile int y_cur = 0;
+volatile int x_pos = 0;
+volatile int y_pos = 0;
 
 Ticker ticker;
 
-DigitalOut led4(D8);
-DigitalOut led(D9);
-DigitalOut led2(D10);
-DigitalOut led3(D12);
+DigitalOut mouse_port_right_button(D8);
+DigitalOut mouse_port_left_button(D9);
+DigitalOut mouse_port_forward(D10);
+DigitalOut mouse_port_left(D12);
+
+DigitalOut mouse_port_back(D11);
+DigitalOut mouse_port_right(D13);
+
+
+
+DigitalOut game_port_right_button(PC_5);
+DigitalOut game_port_left_button(PC_6);
+DigitalOut game_port_forward(PC_8);
+DigitalOut game_port_left(PC_10);
+
+DigitalOut game_port_back(PC_12);
+DigitalOut game_port_right(PC_11);
+
+
 
 //DigitalOut led_blue(LED3);
 
-DigitalOut mouse_port_back(D11);
-DigitalOut mouse_port_left(D13);
-
 void update_quadrature() 
 {
     //led_blue = !led_blue;
@@ -52,33 +68,90 @@
         }      
         
         mouse_port_back = ((x_cur + 1)/2) & 1;
-        mouse_port_left = (x_cur/2) & 0x1;
+        mouse_port_right = (x_cur/2) & 0x1;
         
-        led2 = ((y_cur + 1)/2) & 1;
-        led3 = (y_cur/2) & 0x1;
+        mouse_port_forward = ((y_cur + 1)/2) & 1;
+        mouse_port_left = (y_cur/2) & 0x1;
     }
 }
 
-
 void onMouseEvent(uint8_t buttons, int16_t x, int16_t y, int8_t z) {
     //printf("ME: buttons: %d, x: %d, y: %d, z: %d\r\n", buttons, x, y, z);
     
+    // Disabling ticker as a quick method of disabling the ticker intterrupt.
+    // This is to stop the IRQ being called when we have updated x_pos but not
+    // y_pos.
+    ticker.detach();
+    
     x_pos += x;
     y_pos += y;
     
-    led = ~buttons & 1;
-    led4 = ~buttons & 0x2;
+    // This should be enough to calculate the pulse width.  As we just want to
+    // calculate the speed we need to pulse to not lag.
+    
+    int dist = max(abs(x_pos),abs(y_pos)) * 30;
+    
+    dist = dist > MAX_QUADRATURE_FREQ ? MAX_QUADRATURE_FREQ : dist; /* Cap frequency to 3khz. */  
+    
+    mouse_port_left_button = ~buttons & 1;
+    mouse_port_right_button = ~buttons & 0x2;
     
+    if (x_pos != 0 || y_pos != 0)
+    {
+        ticker.attach(update_quadrature, 1.0 / dist);
+        
+        //ticker.attach_us(update_quadrature, 1000000 / dist);
+    }
+    Thread::yield();
+}
 
-}
+uint32_t prev_buttons = 0;
+int prev_x, prev_y;
 
 void onJSEvent(uint32_t buttons, int8_t x, int8_t y, int16_t x_b, int16_t y_b) {
-    printf("buttons: %x, x: %d, y: %d, x_b: %d y_b:%d\r\n", buttons, x, y, x_b, y_b);
+    //printf("buttons: %x, x: %d, y: %d, x_b: %d y_b:%d  :", buttons, x, y, x_b, y_b);
+
+
+    game_port_left_button = ~buttons & 0x40;
+    game_port_right_button = ~buttons & 0x10;
+    
+    if (buttons & 0x100000)
+    {
+        if (prev_buttons & 0x100000)
+        {
+            //printf("x:%d y:%d", prev_x - x_b, prev_y - y_b);
+            
+            ticker.detach();
+            
+            x_pos += -((prev_x - x_b)/128);
+            y_pos += (prev_y - y_b)/128;
+            
+            int dist = max(abs(x_pos),abs(y_pos)) * 30;
     
-    led = ~buttons & 1;
-    led4 = ~buttons & 0x2;
-         
+            dist = dist > MAX_QUADRATURE_FREQ ? MAX_QUADRATURE_FREQ : dist; /* Cap frequency to 3khz. */  
+            
+            if (x_pos != 0 || y_pos != 0)
+            {
+                ticker.attach(update_quadrature, 1.0 / dist);
+                
+                //ticker.attach_us(update_quadrature, 1000000 / dist);
+            }
+        }
+        prev_x = x_b; prev_y = y_b;
+    }
+    //printf("\r\n");
+    
+    prev_buttons = buttons;
+    
+    mouse_port_left_button = ~buttons & 2;
+    mouse_port_right_button = ~buttons & 0x1;
+    
+    game_port_forward = !( y > 40);
+    game_port_back = !( y < -40 );
+    game_port_left = !( x < -40 );
+    game_port_right = !( x > 40 );     
     //Thread::wait(1);
+    Thread::yield();
 }
  
 void mouse_task(void const *) {
@@ -193,9 +266,23 @@
 }
 
 int main() 
-{
-    ticker.attach(update_quadrature, 1.0 / 5000);
+{   
+    mouse_port_left_button = 1;
+    mouse_port_right_button = 1;
     
+    mouse_port_back = 1;
+    mouse_port_right = 1;
+    mouse_port_forward = 1;
+    mouse_port_left = 1;
+        
+    game_port_left_button = 1;
+    game_port_right_button = 1;
+        
+    game_port_forward = 1;
+    game_port_back = 1;
+    game_port_left = 1;
+    game_port_right = 1;     
+
     //Thread msdTask(msd_task, NULL, osPriorityNormal, 1024 * 4);
     Thread mouseTask(mouse_task, NULL, osPriorityNormal, 2048* 4);
     Thread steamctrlTask(steamctrl_task, NULL, osPriorityNormal, 2048* 4);