Índice general Foros Digital, Electricidad e Informática Controlando Microsoft Train Simulator con un Arduino

Controlando Microsoft Train Simulator con un Arduino

Moderador: 241-2001



Desconectado
Mensajes: 93
Ubicación: Valencia, España
Registrado: 31 Ene 2014 08:55
Hace un par de días vi un dispositivo por Internet llamado Rail Driver, que probablemente ya conoceréis todos así que me ahorro la explicación. Sin embargo, el precio me aparta bastante: 199$, ~150€ al cambio. Puede que haya a gente que le interese, pero yo lo considero excesivamente caro, para las contadas veces que uso el MSTS. Así que estuve mirando alternativas, como el controlador Densha De Go, para consolas, pero fácilmente adaptable a ordenador, pero que tiene pocas cosas.

Viendo que las soluciones comerciales no me satisfacen, se me ocurrió: "oye, si total son un puñado de potenciómetros y botones, ¿y si pruebas tú a hacer uno?". Eso sí me gustó. Sin embargo, quería que no fuese como el Rail Driver, que simula el teclado, sino que debía ir de verdad, sin necesidad de tener abierto permanentemente el menú de F5, sino que debía ser todo transparente para el usuario.

Este martes pues me puse a hacer una de las cosas que más me gusta: ingeniería inversa. Con OllyDBG, abrí el ejecutable de MSTS y me puse a cotillear cómo funcionaba internamente el tema de los controles. Este jueves averigüé finalmente el funcionamiento de los controles esenciales.

Ayer me decidí a ir por lo práctico y emplear un Arduino como interfaz para la parte física. Dos horas y tenía funcionando el protocolo de comunicación entre PC y Arduino, y otras dos horas y tenía funcionando la comunicación con el MSTS.

El resultado podéis verlo aquí:


Quedan todavía bastantes cosas (por ejemplo, el velocímetro), pero me lo he montado bien y añadir botones nuevos son cosa de añadir un par de líneas al código del Arduino, y adaptar el receptor para procesar los datos y transferirlos al MSTS.


Desconectado
Mensajes: 203
Registrado: 20 Ene 2014 20:04
Sería una verdadera obra a la hora de compartir este proyecto, el publicar el código fuente en C del Arduino. Aunque, teniendo ya un 80x86 de Intel (PC), no sé para que demonios quieres ir marcha atrás y meterle el microcontrolador que usa el Arduino (a no ser que sea para hacer ver a la opinión pública lo bueno que eres).

Saludos.


Desconectado
Mensajes: 93
Ubicación: Valencia, España
Registrado: 31 Ene 2014 08:55
A parte de que el sistema operativo en que se ejecuta el MSTS no está diseñado para aplicaciones realtime como es gestionar las entradas de usuario, los Intel son procesadores, mientras que aquí empleo tanto un procesador (el núcleo AVR) como los periféricos asociados de los que un Intel carece: dos ADCs para los potenciometros y dos GPIO para las entradas digitales de los botones, y en poco un PWM al menos para modular el voltaje del velocímetro analógico (más si me decido al final por ponerle también medidor de voltaje en catenaria o presión de los frenos).

Tu teclado y tu ratón por ejemplo llevan sus propios procesadores también, basados normalmente en los 8051 de Intel. No creo qué sus fabricantes lo hicieran con la intención de demostrar al público lo buenos que son ;)


Desconectado
Mensajes: 610
Ubicación: Molina de Segura (Murcia)
Registrado: 17 Feb 2009 23:26
Impresionante trabajo. Felicidades.

Queda un poco lejos de los manitas que podamos ser muchos de nosotros.


Desconectado
Mensajes: 221
Registrado: 24 Dic 2009 01:43
pnia escribió:
Queda un poco lejos de los manitas que podamos ser muchos de nosotros.


Bastante lejos...... ;) :lol:

Excelente trabajo socram8888

Saludos.


Desconectado
Mensajes: 93
Ubicación: Valencia, España
Registrado: 31 Ene 2014 08:55
Considero que funciona ya lo suficientemente bien como para publicar aquí todo:

Transmisor - se ejecuta en el Arduino:
#define ARRAY_SIZE(x) sizeof(x) / sizeof(x[0])

static const unsigned int ANALOGIC_RES_VIRTUAL = 100;
static const unsigned int ANALOGIC_RES_PHYSICAL = 1024;
static const float ANALOGIC_RES_RATIO = (float) ANALOGIC_RES_VIRTUAL / (float) ANALOGIC_RES_PHYSICAL;
static const unsigned int ANALOGIC_THRESHOLD = ceil(ANALOGIC_RES_PHYSICAL / ANALOGIC_RES_VIRTUAL);
static const unsigned int TIMEMIN = 10;
static const unsigned int TIMEMAX = 1000; // milliseconds

typedef struct {
  int pin;
  boolean analogic;
} Port;

static const Port PORT[] = {
  { A0, true },
  { A1, true },
  { 2, false },
  { 3, false },
};

typedef struct {
  unsigned int lastvalue;
  unsigned long lasttime;
} PortStatus;

static PortStatus PORT_STATUS[ARRAY_SIZE(PORT)];
static boolean forceUpdate;

void setup() {
  Serial.begin(115200);

  for (int i = 0; i < ARRAY_SIZE(PORT); i++) {
    if (!PORT[i].analogic) {
      digitalWrite(PORT[i].pin, HIGH); // Enable pull-up
    }
  }

  forceUpdate = true;
}

void loop() {
  unsigned long now = millis();

  for (int i = 0; i < ARRAY_SIZE(PORT); i++) {
    boolean update = forceUpdate;
    unsigned char outputValue;

    if (!update) {
      unsigned long passed = now - PORT_STATUS[i].lasttime;
      if (passed <= TIMEMIN) continue;
      update = (passed >= TIMEMAX);
    }

    if (PORT[i].analogic) {
      unsigned int analogValue = analogRead(PORT[i].pin);
 
      if (!update) {
        update = (abs(analogValue - PORT_STATUS[i].lastvalue) >= ANALOGIC_THRESHOLD);
      }
 
      if (update) {
        outputValue = round((float) analogValue * ANALOGIC_RES_RATIO);
        PORT_STATUS[i].lastvalue = analogValue;
      }
    } else {
      outputValue = !digitalRead(PORT[i].pin); // Not because active-low

      if (!update) {
        update = (PORT_STATUS[i].lastvalue != outputValue);
      }

      if (update) {
        PORT_STATUS[i].lastvalue = outputValue;
      }
    }

    if (update) {
      Serial.write(0x80 + i);
      Serial.write(outputValue);
      PORT_STATUS[i].lasttime = now;
    }
  }

  forceUpdate = false;
}


Receptor, ejecutándose en el PC. Programado en el lenguaje de prototipado AutoIT3:

#include <WinAPI.au3>
#include <ProcessConstants.au3>
#include "CommInterface.au3"

Func __ReadProcessData($hProcess, $iAddress, $sWhat)
   Local $hBuffer = DllStructCreate($sWhat)
   Local $iRead, $iSize

   $iSize = DllStructGetSize($hBuffer)
   If Not _WinAPI_ReadProcessMemory($hProcess, $iAddress, DllStructGetPtr($hBuffer), $iSize, $iRead) Or $iSize <> $iRead Then
     SetError(1)
     Return False
   EndIf

   Return DllStructGetData($hBuffer, 1)
EndFunc

Func __WriteProcessData($hProcess, $iAddress, $sWhat, $sData)
   Local $hBuffer = DllStructCreate($sWhat)
   Local $iRead, $iSize
   DllStructSetData($hBuffer, 1, $sData)

   $iSize = DllStructGetSize($hBuffer)
   If Not _WinAPI_WriteProcessMemory($hProcess, $iAddress, DllStructGetPtr($hBuffer), $iSize, $iRead) Or $iSize <> $iRead Then
     SetError(1)
     Return False
   EndIf

   Return True
EndFunc

Func __ReadProcessPtrs($hProcess, $iAddresses)
   Local $i
   Local $iPtr = $iAddresses[0]

   For $i = 1 To UBound($iAddresses) - 1
     $iPtr = __ReadProcessData($hProcess, $iPtr, "ptr")
    

     If @error Or $iPtr = 0 Then
       SetError(1)
       Return False
     EndIf

     $iPtr += $iAddresses[$i]
   Next

   Return $iPtr
EndFunc

Func __ReadThrottlePtr($hProcess)
   Local Const $iOffsets[4] = [0x7C8778, 0x72, 0x8, 0x8C] ; 8C+[8+[72+[7C8778]]]
   Local $iPtr

   $iPtr = __ReadProcessPtrs($hProcess, $iOffsets)
   If @error Then
     SetError(1)
     Return False
   EndIf

   Return $iPtr
EndFunc

Func ReadThrottle($hProcess)
   Local $iPtr, $fThrottle

   $iPtr = __ReadThrottlePtr($hProcess)
   If @error Then
     SetError(1)
     Return False
   EndIf

   $fThrottle = __ReadProcessData($hProcess, $iPtr, "float")
   If @error Then
     SetError(2)
     Return False
   EndIf

   Return $fThrottle
EndFunc

Func WriteThrottle($hProcess, $fThrottle)
   Local $iPtr

   $iPtr = __ReadThrottlePtr($hProcess)
   If @error Then
     SetError(1)
     Return False
   EndIf

   ConsoleWrite(hex($iPtr, 8) & @crlf)
   __WriteProcessData($hProcess, $iPtr, "float", $fThrottle)
   If @error Then
     SetError(2)
     Return False
   EndIf

   Return True
EndFunc

Func __ReadBrakePtr($hProcess)
   Local Const $iOffsets[4] = [0x7C8778, 0x72, 0x8, 0x128] ; 8C+[8+[72+[7C8778]]]
   Local $iPtr

   $iPtr = __ReadProcessPtrs($hProcess, $iOffsets)
   If @error Then
     SetError(1)
     Return False
   EndIf

   Return $iPtr
EndFunc

Func WriteBrake($hProcess, $fThrottle)
   Local $iPtr, $fSpeed

   $iPtr = __ReadBrakePtr($hProcess)
   If @error Then
     SetError(1)
     Return False
   EndIf

   __WriteProcessData($hProcess, $iPtr, "float", $fThrottle)
   If @error Then
     SetError(2)
     Return False
   EndIf

   Return True
EndFunc

Func __ReadInverterModePtr($hProcess)
   Local Const $iOffsets[4] = [0x7C8778, 0x72, 0x8, 0xB8] ; 8C+[8+[72+[7C8778]]]
   Local $iPtr

   $iPtr = __ReadProcessPtrs($hProcess, $iOffsets)
   If @error Then
     SetError(1)
     Return False
   EndIf

   Return $iPtr
EndFunc

Func __ReadInverterMultiplierPtr($hProcess)
   Local Const $iOffsets[4] = [0x7C8778, 0x72, 0x8, 0xCA] ; 8C+[8+[72+[7C8778]]]
   Local $iPtr

   $iPtr = __ReadProcessPtrs($hProcess, $iOffsets)
   If @error Then
     SetError(1)
     Return False
   EndIf

   Return $iPtr
EndFunc

Func WriteInverter($hProcess, $iInverter)
   Local $iPtr, $iMultiplier

   $iPtr = __ReadInverterModePtr($hProcess)
   If @error Then
     SetError(1)
     Return False
   EndIf

   __WriteProcessData($hProcess, $iPtr, "byte", $iInverter)
   If @error Then
     SetError(2)
     Return False
   EndIf

   $iPtr = __ReadInverterMultiplierPtr($hProcess)
   If @error Then
     SetError(1)
     Return False
   EndIf

   Switch $iInverter
     Case 2
       $iMultiplier = 0x3F80
     case 1
       $iMultiplier = 0
     case 0
       $iMultiplier = 0xBF80
   EndSwitch

   __WriteProcessData($hProcess, $iPtr, "ushort", $iMultiplier)
   If @error Then
     SetError(2)
     Return False
   EndIf

   Return True
EndFunc

Func __ReadHornPtr($hProcess)
   Local Const $iOffsets[4] = [0x7C8778, 0x72, 0x8, 0x1E0]
   Local $iPtr

   $iPtr = __ReadProcessPtrs($hProcess, $iOffsets)
   If @error Then
     SetError(1)
     Return False
   EndIf

   Return $iPtr
EndFunc

Func WriteHorn($hProcess, $fHorn)
   $iPtr = __ReadHornPtr($hProcess)
   If @error Then
     SetError(1)
     Return False
   EndIf

   __WriteProcessData($hProcess, $iPtr, "float", $fHorn)
   If @error Then
     SetError(2)
     Return False
   EndIf

   Return True
EndFunc

$hSerial = _CommAPI_OpenCOMPort(2, 115200, "N", 8, 0)
If @error Then
   ConsoleWrite("Cannot open serial")
   Exit(1)
EndIf

_CommAPI_ClearCommError($hSerial)

$iPIDs = ProcessList("train.exe")
If $iPIDs[0][0] = 0 Then
   ConsoleWrite("Cannot find running TRAIN.EXE")
   Exit(2)
EndIf

$hProcess = _WinAPI_OpenProcess($PROCESS_VM_READ + $PROCESS_VM_WRITE + $PROCESS_VM_OPERATION, False, $iPIDs[1][1])
If @error Or Not $hProcess Then
   ConsoleWrite("Cannot open process")
   Exit(2)
EndIf

While True
   $iCode = _CommAPI_ReceiveBinary($hSerial, -1, 1)
   If @error Then
     ConsoleWriteError("Cannot read from serial")
     ExitLoop
   EndIf

   $iCode = Asc($iCode)
   If $iCode >= 0x80 Then
     Switch $iCode
       Case 0x80
         $iCode = _CommAPI_ReceiveBinary($hSerial, -1, 1)

         If @error Then
            ConsoleWriteError("Cannot read from serial")
            ExitLoop
         EndIf

         $iCode = Asc($iCode)
         if ($iCode <= 100) Then
            WriteThrottle($hProcess, $iCode / 100.0)
         EndIf
       Case 0x81
         $iCode = _CommAPI_ReceiveBinary($hSerial, -1, 1)

         If @error Then
            ConsoleWriteError("Cannot read from serial")
            ExitLoop
         EndIf

         $iCode = Asc($iCode)
         if ($iCode <= 100) Then
            WriteBrake($hProcess, $iCode / 100.0)
         EndIf
       Case 0x82
         $iCode = _CommAPI_ReceiveBinary($hSerial, -1, 1)

         If @error Then
            ConsoleWriteError("Cannot read from serial")
            ExitLoop
         EndIf

         $fThrottle = readthrottle($hprocess)
         WriteThrottle($hprocess, 0)
         if $iCode <> Chr(0) Then
            WriteInverter($hprocess, 2)
         Else
            WriteInverter($hprocess, 1)
         EndIf
         WriteThrottle($hprocess, $fThrottle)
       Case 0x83
         $iCode = _CommAPI_ReceiveBinary($hSerial, -1, 1)

         If @error Then
            ConsoleWriteError("Cannot read from serial")
            ExitLoop
         EndIf

         if $iCode <> Chr(0) Then
            WriteHorn($hProcess, 1.0)
         Else
            WriteHorn($hProcess, 0.0)
         EndIf
       Case Else
         ConsoleWriteError("Illegal command byte: " & Hex($iCode, 2) & @CRLF)
     EndSwitch
   EndIf
WEnd

Éste último requiere la librería CommAPI (http://www.autoitscript.com/wiki/CommAPI) y ejecutarse como administrador (para poder acceder a la memoria de otro proceso) una vez que el MSTS se está ya ejecutando.
Última edición por socram8888 el 03 Jun 2014 10:07, editado 1 vez en total


Desconectado
Mensajes: 1
Registrado: 13 Abr 2014 07:06
Soy nuevo en el Foro

Tengo problemas para gobernar con Arduino el K83 , no se que hago mal con una señal pwm 106 con el pin 13 ,es el codigo del primer devio

Creo que me falta ,informacion


Volver a Digital, Electricidad e Informática

Síguenos en Facebook Síguenos en Youtube Síguenos en Instagram Feed - Nuevos Temas
©2017   -   Información Legal