Standalone ticket checking machine - could it be done with Arduino?

Hello, I'm a newbie in Arduino (and in programming in general), and this is my first post, so sorry if some details may be excessive.
I have a "vintage" tabletop device for reading certain type of magnetic tickets. It has no "model name" and was custom-made a long time ago (around mid 90's) by unknown vendor, so no documentation is available. It works fine on a PC via any serial-terminal program, but i wanted to make a standalone "ticket checker" with a 1602 i2c display. The device have a serial port (true RS-232 with appropriate signal levels) which can work only at 9600 baud.
With a PC it works as follows:

  1. Device boots up and waits for a command from a PC.
  2. PC sends a command "read0"
    3)Device receives a command, changes it's mode from IDLE to READING and sends ASCII <ACK> symbol to a PC. In this mode device can stay for as long as needed.
  3. User puts his ticket into device
  4. The device reads the ticket and sends ASCII string to the PC in the following format:
    <STX>00;<41 varying data symbols><ETX><ETX>
    If there is an issue with a reading, the device will send 5 symbols of error code (<STX><2 symbols of error code><ETX><ETX>). After that device is ready to receive Eject0 command.
  5. PC sends "EJECT0" comand to the device
  6. Device sends <ACK> to the PC and returns ticket to user.
    8)Device is ready for a next cycle.

If this cycle is interrupted at any time by unexpected command (for example, if void loop() starts again and sends READ0 command before device is ready for a next cycle) - device will halt and will need powercycle. So my goal is to make Arduino to send command 'read0' to the device over Serial1, WAIT until the ticket is inserted and the data fully arrives, display ASCII string of data on 1602 LCD display, send 'eject0' command, send 'read0'.
I've connected Device to Mega2560 via RS232-TTL adapter and ran this simple setch to check if communication is working fine:

//read0 and eject0 contain unprintable STX and ETX characters, they can only be stored as an array of chars and printed via Serial1.print(). Declaring commands as strings or bytes and sending them via Serial1.write() does not work (probably has something to do with a way Arduino handles control characters/
char read0[5] = "12";
char eject0[6] = "41"; 

void setup() {

  Serial1.begin (9600);           //Serial port-1 setup to appropriate speed
}

void loop()                         //This is only a command sending test, nothing will be displayed
{
  Serial1.print(read0); //Sending ASCII string equal to HEX command to Serial1
  delay(5000);
  Serial1.print(eject0); //Sending ASCII string equal to HEX command to Serial1
  delay(3000);
}

Scetch works fine, if i manage to insert the ticket just in time, but I can't use delays in a project since i don't know exact timing in which ticket would be presented and read. If EJECT0 or read0 command will be sent before execution of previous READ0 is finished, device will halt untill manual reboot. I've read Serial Communication basics and achieved ticket data displaying in a serial monitor, but the process eventually hangs - seems like arduino keeps spamming serial1 and the device stops.

//read0 and eject0 can only be stored as an array of chars and printed via Serial1.print(). Declaring commands as strings or bytes and sending them via Serial1.write() does not work (probably has something to do with a way Arduino handles control characters/
char read0[5] = "12";
char eject0[6] = "41";
//It is unknown how many chars will be received, but the maximum is less than 50
const byte numChars = 50;
char receivedChars[numChars];

boolean newData = false;

void setup() {
  Serial1.begin(9600);
  Serial.begin(115200);
  Serial.println("<Arduino is ready>");

}

void loop() {
  delay(2000);
  readCard();
  recvWithStartEndMarkers();
  showNewData();
  returnCard();
}

void readCard() {//Sending read0 command

  Serial1.print(read0);

}
void recvWithStartEndMarkers() {//receiving data. start and end markers are unprintable symbols for STX and ETX respectively
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '';
  char endMarker = '';
  char rc;

  while (Serial1.available() > 0 && newData == false) {
    rc = Serial1.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }

    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

void showNewData() {
  if (newData == true) {
    Serial.print("Ticket data: ");
    Serial.println(receivedChars);
    newData = false;
    delay(2000);

  }

}
void returnCard() {
  Serial1.print(eject0);
  delay(2000);


}
//This sketch will work if user is present at all times and constantly inserting tickets. But if there is a slight delay between read0 command is issued and ticket is inserted - process stops and the device needs powercycle

So basically i want to ask:

  1. Is Arduino even suitable for my project? Void loop() runs so fast and i can't seem to find any way to make it stop further execution until long (ticket data) or short (error code) response is received and displayed. If it is not, maybe there is a the better platform for beginners?
  2. If it is possible on Arduino, can you show me the way I should look forward to? Currently I'm looking into state machines, but at this point it seems unclear if SWITCH CASE is able to do exactly what i need.
2 Likes

Hello

Welcome to the worldbest Arduino forum ever.

Take view into the IDE to find the example "Serial Event".

You might use this example modified to read the input strings sent by the PC to get a proper started in your project.

Have a nice day and enjoy coding in C++.

1 Like

Thank you for the clue! This looks promising, I will search more about SerialEvent.

A state machine should work. You have a very well defined sequence of events:

1> Send READ0 command
2> Wait for < ACK >
3> Wait for a complete message to be received. A complete message being indicated by a text string that begins with < STX > and ends with < ETX >< ETX >. The Serial Input Basics example code recvWithStartEndMarkers() can be modified to use the double < ETX > as an end marker.
4> Process the message, either displaying the ticket data on the LCD, or taking whatever action you desire for an error code.
5> Send the EJECT0 command
6> Wait for < ACK >
7> go back to step 1

You actually want loop() to keep running repeatedly. While waiting for a response, each iteration of loop you are going to enter the state that calls the recvWithStartEndMarkers() function, slowly reading in characters until the complete message is received (with the processor running at 16MHz, 9600 baud is very slow, a single character takes a bit over 1 millisecond to transmit).

Nah. Usually the exact opposite applies - important details have to be laboriously drawn out piecemeal. Which is aggravating. Nice first post!

2 Likes

The above comments are not correct and suggest you were doing something wrong. A much better way to deal with unprintable characters in code is to use array initialization in decimal or hexadecimal representation, like this (using decimal)


#define STX 2
#define ETX 3
#define ACK 6
char eject0[5] = {STX, '4','1',ETX,ACK};  //C-string terminating zero not required for binary data
char startMarker = STX;
...
Serial.write(eject0,5);  //send 5 character eject command
2 Likes

The problem is, the ACK arrives to arduino almost immediately affter any command is sent. That's how the device confirms it received a command. Maybe it was useful in 90's when PC's were slow and the soft was unstable. :grinning: Thats why I thought checking for ACK is not useful in this case - in a normal workflow there is no issues with command sending, if there is a fault (like sending next command without waiting for previous command's execution) - device will halt until powercycle.

I don't have this-level of understanding of how state machines work yet, but the problem for me seems like this: void loop() starts with a command read0 sending procedure, and each iteration will send this command without waiting, which will cause device halt.

Thats what I thought in the first place! I know this realization looks ugly, but this was the only way the device accepted commands from arduino. I also tried defining control characters before (as chars with and without C-string terminating, as bytes etc), result was always the same - no command is recognized on device-side.
I tried to use your example in a simple command-sending scetch and it WORKS with Serial1.write() (at first i was unattentive and forgot to check which Serial is in use)! Here it is:

#define STX 2
#define ETX 3
#define ACK 6

void setup() {

  Serial1.begin (9600);           //Serial port-1 setup to appropriate speed
}

void loop()                         //This is only a command sending test, nothing will be displayed
{
  char eject0[5] = {STX, '4', '1', ETX, ACK}; //C-string terminating zero not required for binary data
  char read0[4] = {STX, '1', ETX, '2'};
  Serial1.write(read0, 4); //send 4 character read command
  delay(5000); //just wait
  Serial1.write(eject0, 5);//send 5 character eject command
  delay(2000); //just wait
}

Actually it takes quite a long time in terms of processor time. When you print() the READ0 command, it is placed in the transmit buffer, then the code continues while it is being sent. At 9600 baud, each character takes 1.04mS to send, and the reception of the ACK will take another 1.04mS (since the ACK cannot be sent until after the command is received). In this amount of time, the processor can execute 10's of thousands of instructions. You also have to account for the ACK being present in the receive buffer.
It really does no harm to ignore the ACK, but it can be used as an indicator that the command was not properly received, so that an appropriate action can be taken, either re-sending the command, or informing you on the LCD that the device may need to be reset.

Can you write out the exact character sequence needed for the read0 and eject0 commands? I couldn't even see the embedded control characters until I tried to quote the lines of code, and what I am seeing is a bit confusing. The '2' at the end of read0 seems odd, should that be instead <STX> 1 <ETX><ETX>? Does eject0 really need to end with <ACK>, or should that be <STX>41<ETX><ETX>?

If you want to write the code so the control characters are visible, you can use octal numbers. Quoting everything separately is necessary, otherwise the ASCII numbers would be taken as part of the octal number. The compiler will concatenate all adjacent quoted text into a single string.

//read0 and eject0 contain unprintable STX and ETX characters, they can only be stored as an array of chars and printed via Serial1.print(). Declaring commands as strings or bytes and sending them via Serial1.write() does not work (probably has something to do with a way Arduino handles control characters/
char read0[5] = "\002" "1" "\003" "2";
char eject0[6] = "\002" "41" "\003" "\006"; 

Yes, it is confusing, but you got it right: read commands consist of 4 characters sequence - STX, 1, ETX, 2. Eject is 5 character sequence - STX, 4,1, ETX, ACK.

It sounds as a nifty feature, but at this point it is still unclear if it is even possible to run loop() without halting the device.

Thank you so much!!! This actually works, device is now recognizing commands sent via Serial1.write() and it looks so much better!

Main issue was resolved by adding void waitForCard() after 'void readCard' with a while loop:

void waitForCard() {


  while (Serial1.available() < 5 )
  { delay(1000);
   
  }


}

... and by erasing what's left in an incoming buffer and data variable at the end of iteration of void loop():

void flushSerial() {
  while (Serial1.available() > 0) {
    char t = Serial1.read();
  }
receivedChars[numChars] = '0';
   delay(3000);
}

void recvWithStartEndMarkers() will write it's result to an array of chars char receivedChars, depending on the result it will be 5 or 50 chars long (5 for error code and 50 for actual data) and now i need to extract chars at specific positions and put them in variable so i can match them with a ticket type and remaining value.
Array itself will look like this
00;<48>766496766548568465<685>4856542122x for data (with <> braces are chars i need to extract, there are no actual braces in the data. x - is irrelivant end sentinel and is often varies)
or like this
x35xx - for errors (numbers are variable, x - unprintable control symbols).
So as you can see, i have no delimeters (except after two leading zeros at the start), only known position of chars. Is there any way to extract them into another array?

Forgive me if I am misunderstanding your question. Would it be possible to access array elements in the usual way, with square brackets?

// Example data
char receivedChars[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };

// New array
char extracted[2];

// Copy individual elements
extracted[0] = receivedChars[2];
extracted[1] = receivedChars[3];

// extracted should now equal { 'c', 'd' }

If you wanted to copy more than a few elements at once, you might want to use some kind of loop, or the memcpy() function.

1 Like

Yes! That is exactly what i was looking for!
Thank you!

Big THANKS to everyone who commented!
Also, note to anyone who will want to use char array in an if() statement. You can't use char array like this:

// Example data
char receivedChars[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };

// New array
char extracted[2];

// Copy individual elements
extracted[0] = receivedChars[2];
extracted[1] = receivedChars[3];

// extracted should now equal { 'c', 'd' }
if (extracted == 'cd'){
do something;}

But, you can insted use individual chars from an array like this:

// Example data
char receivedChars[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };

// New array
char extractedA;
char extractedB;

// Copy individual elements
extractedA = receivedChars[2];
extractedB = receivedChars[3];

// extractedA should now equal 'c', and extractedB - 'd' 

if (extractedA == 'c' && extractedB == 'd'){
do something;}

I know this might be obvious to pro's, but I've wasted a lot of time stubbornly trying to use char array in if() without data type conversion. Probably there is a better way to do it, but it worked for me and this might be helpful for someone else.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.