Proyecto: digitalización locomotora LGB años 40.

Continuación del código:

// Aquí el programa pirncipal.
void main()
{
  
  //@ Mi arduino lo detecta en el puerto 28. Así que abrimos el
  //  puerto serie.
	DWORD dwErr = Serial.begin(28,9600,SERIAL_8N2);
	if ( dwErr!=NO_ERROR ) {
		printf("No se puede abrir el puerto!");
		return ;
	}
  //@ Otro de mis intentos de reducir errores.
  Serial.timeouts();
  
	
  bool exit=false;
  bool automatic=false;
  int result;
  velocidad = 0;
  DWORD timer;
  long errorcount=0;
  // bucle infinito.
  // Si hay una tecla ejecuta la correspondiente acción asociada a ella.
  while ( !exit ) 
  {
    clrscr();
    gotoxy(1,1);
    printf("[ESC] Salir [Up] Subir [Dn] Bajar [Rt][Lt] Direccion [Space-Leer] [Enter] Poner\n");
    printf("[p] Auto [s] Parar [1..8] Cambiar vias\n");
    printf("Velocidad: %d Velocidad deseada: %d\n", velocidad, velocidad_deseada);
    printf("Direccion: %d \n", direccion);
    printf("Vias: %d\n", vias);
    printf("Contador de errores: %d\n", errorcount);
    printf("Error: %s\n", errorString[error]);
    printf("%s\n", automatic?"SI":"NO");
    if ( kbhit() )
    {
      char ch = getch();
      switch ( ch ) {
        case 72: if ( velocidad_deseada < 255 ) velocidad_deseada++; break;
        case 80: if ( velocidad_deseada > 0 )   velocidad_deseada--; break;
        case 75: pondireccion(2); break;
        case 77: pondireccion(1); break;
        case 32: leerVelocidad(); break;
        case 13: ponerVelocidad(velocidad_deseada); break;
        case 27: exit=true;
        case 83: parar(); break;
        case 115: parar(); break;
        case 90: leerVias(); break;
        case 122: leerVias(); break;
        case 49: cambiarVia(1); break;
        case 50: cambiarVia(2); break;
        case 51: cambiarVia(3); break;
        case 52: cambiarVia(4); break;
        case 53: cambiarVia(5); break;
        case 54: cambiarVia(6); break;
        case 55: cambiarVia(7); break;
        case 56: cambiarVia(8); break;
        case 112: automatic=!automatic; timer=millis(); break;
        default: break;
      }
    if ( error!=0 ) errorcount++;
    }
    else
    {
      // Para que no de pantallazos.
      delay(100);
    }
    if ( automatic )
    {
      // En modo automatico lee la locomotora cada 5s aunque no se haya pulsado
      // una tecla.
      if ( millis() > timer + 5000 ) {
        leerVelocidad();
        timer = millis();
      }
    }
  }

  //@ Al salir del programa cerramos el puerto de comunicaciones.
  Serial.close();
}

También lo añado como attachment para aquel que quiera descargarlo.

main.cpp (6.86 KB)

Obviamente, sin el código del mando, me direis que no se puede hacer nada:

#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>

#define NODE 00

#define MAX_BUFFER_LEN 32

struct message {
  unsigned int function;
  unsigned int value;
  unsigned int option;
};

struct query {
  unsigned int to;
  message msg;
};

query q;

RF24 radio(9,10);           // La radio.
RF24Network network(radio); // La red.
bool chipConnected;         // Indica que hemos detectado el chip.

// Hace parpadear un pin. Me sirve para indicar que la radio está o
// conectada.
void flash(int pin) {
  digitalWrite(pin,HIGH);
  delay(500);
  digitalWrite(pin,LOW);
  delay(500);  
}

// Lo mismo, pero le indicamos cuantas veces (y el tiempo).
void flash(int pin, int many, int duration=500) {
  for (int i=0; i<many; i++) {
     digitalWrite(pin, HIGH);
     delay(duration);
     digitalWrite(pin, LOW);
     delay(duration);
  }
}

// Sirve para comprobar que en el puerto serie tenemos datos y leemos
// lo que hemos de enviar.
int update_query(query &q) {
  int i;
  byte frame[MAX_BUFFER_LEN];
  byte b;
  // Si hay datos en el puerto serie los leemos.
  i=0;
  if ( Serial.available() ) {
    while ( Serial.available() ) {
      b = Serial.read();
      if ( i<MAX_BUFFER_LEN ) frame[i]=b; 
      i++;
      delayMicroseconds(1562); //  OJO! sin esto no funciona.
    }
  }
  else
    return 0; // Si no hay datos no hacemos nada.
  
  // El frame es fijo y es 8. Retornamos de la funcion y salimos sin hacer nada.
  // esto provocara un error de timeout en el ordenador.
  if ( i!=8 ) { Serial.flush(); return 0; } 

  // No hacemos otro tipo de corrección. En el frame esta el valor query y lo 
  // obtenemos.
  q.to           = frame[1]<<8 | frame[0];
  q.msg.function = frame[3]<<8 | frame[2];
  q.msg.value    = frame[5]<<8 | frame[4];
  q.msg.option   = frame[7]<<8 | frame[6];
  
  return 1;
}

// Responder a una consulta. Solo envia la consulta por el puerto serie.
int response_query(query &q) {
  Serial.write((unsigned char*)&q, sizeof(query));
  Serial.flush();
  //delayMicroseconds(37000);
}

// Escribe en la red, la consulta.
bool netWrite(query &q) {
  RF24NetworkHeader h(q.to);
  return network.write(h, &q.msg, sizeof(message));
}

// Lee de la red una consulta.
bool netRead(query &q) {
  RF24NetworkHeader h;
  network.read(h, &q.msg, sizeof(message));
  q.to = h.from_node;
}

void setup() {
  Serial.begin(9600,SERIAL_8N2);
  while(!Serial);
  pinMode(2,OUTPUT);
  digitalWrite(2,LOW);

  radio.begin();
  network.begin(90,NODE);
  chipConnected = radio.isChipConnected();
}

void loop() 
{
  network.update(); // Comprobamos la red.
  // Si hay un paquete disponible se lo mandamos al PC.
  if ( network.available() ) { netRead(q); response_query(q); }
  // Si hay un mensaje por parte del PC, lo enviamo a la red
  if ( update_query(q) )
  {
    // El paquete es para la red, no para el mando, así que lo escribe.
    if ( q.to!=00 )
      netWrite(q);
    else
    {
      // El paquete es para el mando, de momento solo sirve para ver
      // si el chip esta conectado {00,00,00,00} y hacer que se encienda
      // un led en el pin 2 {00, 01, 00, 00}. 
      if ( q.msg.function=0x00 ) q.msg.value=radio.isChipConnected();
      if ( q.msg.function=0x01 ) digitalWrite(2, q.msg.value);
      // Responde directamente, no tiene que esperar a que hay un mensaje
      // de radio ni nada.
      response_query(q);
    }
  }
}

Voy a explicaros un poco la libreria serial que utilizo. La libreria la desarrolle hace un tiempo, cuando era aficionado a los GPS y tuve la necesidad de leer datos de un GPS Garmin en el ordenador. La libreria funcionaba bien con él. Con el tiempo la he ido adaptando. Cuando me enfrenté a la tarea de conectar un pc y convertirlo en un maestro Modbus, la cambié un poco. Y luego vi que me gustaba mas la forma de las funciones de la libreria Serial de Arduino y la reescribí otra vez un poco.

Básicamente utiliza la api de win32 y las funciones asociadas a él. No explicaré como se hace ya que hay un tuto bastante bueno en la parte de documentación escrito por Metaconta, que por cierto ¿estas por ahí?, creo que tu me podrías ser de ayuda...

La librería consta de la siguientes funciones:

begin

Inicializa el puerto serie, pero no lo abre. Sirve para indicar los parametros de puerto, velocidad, bits de datos, bits de stop y bits de parada. Al principio utilizaba solo una función setConfig que era un poco engorrosa de llamar. Así que al final se ha quedado en la forma de Arduino:

    Serial1.begin(1,9600,SERIAL_8N2);
    Serial2.begin(2,9600); // Por defecto es SERIAL_8N2 usado en el modbus.

open/close

Abre o cierra el puerto serie. En el caso de la apertura, crea un handle al fichero, y prepara las estructuras necesarias para su uso. En caso de fallo devuelve
el error. Para saber si el puerto serie esta abierto se puede llamar a la función isOpen(..)

read/write

Lectura/escritura del puerto serie. Igualmente que en Arduino se puede usar de dos formas: leer o escribir un byte, o un array. De momento no he implementado una funcion print, ni creo que sea necesario por ahora. Ejemplos:

    unsigned char *puntero;
    Serial1.write(b);                       // Escribe un byte.
    Serial1.write(puntero, cantidad_bytes); // Escribe un array.
    Serial2.read(b);                        // Lee un byte.
    Serial2.read(puntero, cantidad_bytes);  // Lee un array.

available

Igualmente que en arduino indica cuantos caracteres hay disponibles en la cola del puerto serie, si no hay ninguno devuelve 0.

Por último quedan las funciones: setRTS(..), restore(..) y timeouts(..) . La primera fue un experimento fallido para intentar controlar el circuito del modbus, poniendo el pin RTS del RS232 conectado a los pins RE/DE del MAX485. Aunque la función trabaja perfectamente, no conseguí que la linea de control cambiará. restore elimina los posibles errores que haya en el RS232 y la última, timeouts, modifica los valores por defecto en los que el RS232 del PC dará un time out.

Si alguien quiere usarla, y sobretodo, si me la quiere corregir, que lo haga, ni licencias ni nada, solo que si le ve un fallo, me lo indique para poder corregirlo. La dejaré como attachment, aunque ya está incluida cuando subí el código del programa en Builder C++.

serial.zip (3.27 KB)