2

I am trying to use Topway display for HMI lcd with Arduino. This display using RS232C protocol at docs it has a "Communication Packet Structure" like this.

enter image description here

So my query should start with AA and end with CC 33 C3 3C. This sample reading code was taking topway youtube channel.

Serial.write(0xaa); // packet head
Serial.write(0x3e); // VP_N16 read command
Serial.write(0x00); // VP_N16 address
Serial.write(0x08);
Serial.write(0x00);
Serial.write(0x00);
Serial.write(0xcc); // packet tail
Serial.write(0x33); // packet tail
Serial.write(0xc3); // packet tail
Serial.write(0x3c); // packet tail

3E is the command (for reading) code and this is the list of all commands. docs over here

enter image description here

The point is parsing of smart LCD's response at the original. They use this code: (at first comment)

     while (!Serial.available()) {}  
     if (Serial.read()==0xaa) {                     // packet head
     packet_OK=1;
     while (!Serial.available()) {}
     if (Serial.read()==0x3e) {                   // packet command
      while (!Serial.available()) {}  
      temp_h=Serial.read();                      // read back value high byte
      while (!Serial.available()) {}  
      temp_l=Serial.read();                      // read back value low byte
      while (!Serial.available()) {}  
      if (!(Serial.read()==0xcc)) {packet_OK=0;} // packet tail
      while (!Serial.available()) {}  
      if (!(Serial.read()==0x33)) {packet_OK=0;} // packet tail
      while (!Serial.available()) {}  
      if (!(Serial.read()==0xc3)) {packet_OK=0;} // packet tail
      while (!Serial.available()) {}  
      if (!(Serial.read()==0x3c)) {packet_OK=0;} // packet tail
      }
      else
      {packet_OK=0;}
     }
     else
     {packet_OK=0;}

It looks nasty and cumbersome because I must write different method for each LCD display commands. Is there any way to read all serial response them as a char array with starting marker "0xAA" and read until finishing marker "0xCC 0x33 0xC3 0x3C" and after that parse it?

You can watch and check this code on youtube

3
  • 2
    It looks like you've put some effort into the post but, what is the question?
    – timemage
    Jul 25 at 16:32
  • you cannot simply wait for the packet tail ... the data payload could contain the same bytes ... you have to keep track of the number of bytes in the message ... them check if packet tail arrives as expected
    – jsotola
    Jul 25 at 17:38
  • 1
    @timemage funny :)
    – mehmet
    Jul 25 at 19:11
6

The short answer to your question is “yes, there is a way”. Multiple ways actually. You could write a blocking function, which follows more or less the logic of the example you show, blocking while waiting for each new byte. Or you could write a non blocking function, which always return immediately and, either gives you the complete packet, or tells you that no full packet has been received so far.

The non-blocking way may be a bit more complicated, but it is more useful, because you program can do other stuff, and be responsive, while waiting for a packet. Here is my take at it. It is based on a finite state machine:

// Return whether the payload of this command is of string type.
bool command_payload_is_string(uint8_t command);

// Return the expected packet length for the provided command,
// excluding header and tail.
int packet_length(uint8_t command);

// Attempt to read a packet, but do not block.
// Return a pointer to a static buffer holding the packet,
// or nullptr if no complete packet was received this time.
uint8_t *read_packet() {
    const uint8_t expected_header = 0xaa;
    const uint8_t expected_tail[4] = {0xcc, 0x33, 0xc3, 0x3c};

    // Return immediately unless we have a byte to process.
    if (Serial.available() == 0) return nullptr;
    uint8_t data = Serial.read();

    static uint8_t buffer[BUFFER_SIZE];
    static int bytes_received, bytes_expected;
    static enum {
        EXPECTING_HEADER, EXPECTING_COMMAND,
        EXPECTING_STRING, EXPECTING_PAYLOAD, EXPECTING_TAIL
    } state = EXPECTING_HEADER;
    switch (state) {
        case EXPECTING_HEADER:
            if (data = expected_header)
                state = EXPECTING_COMMAND;
            break;
        case EXPECTING_COMMAND:
            buffer[0] = data;  // command byte
            bytes_received = 1;
            if (command_payload_is_string(data)) {
                state = EXPECTING_STRING;
                // bytes_expected is not relevant in this case.
            } else {
                state = EXPECTING_PAYLOAD;
                bytes_expected = packet_length(data);
            }
            break;
        case EXPECTING_STRING:
            if (data == '\0') {  // end of string
                buffer[bytes_received++] = '\0';
                state = EXPECTING_TAIL;
                bytes_expected = 4;
                bytes_received = 0;
            } else if (bytes_received < BUFFER_SIZE - 1) {
                buffer[bytes_received++] = data;
            } else {
                // Do nothing. As the string is too long
                // to fit in the buffer, this byte is discarded.
            }
            break;
        case EXPECTING_PAYLOAD:
            buffer[bytes_received++] = data;
            if (bytes_received >= bytes_expected) {
                state = EXPECTING_TAIL;
                bytes_expected = 4;
                bytes_received = 0;
            }
            break;
        case EXPECTING_TAIL:
            // If we don't get the expected tail,
            // give up and wait for a new header.
            if (data != expected_tail[bytes_received++]) {
                state = EXPECTING_HEADER;
                break;
            }
            // If the tail is all right, return the buffer
            // and get ready for the next packet.
            if (bytes_received >= bytes_expected) {
                state = EXPECTING_HEADER;
                return buffer;
            }
    }
    return nullptr;
}

It will be up to you to implement packet_length(). See jstola's comment on why it is needed.

You would use read_packet() like this:

void loop() {
    uint8_t *packet = read_packet();
    if (packet)
        parse_and_handle(packet);

    // the rest of the program, never blocked by read_packet()...
}

Edit: According to your comment, some commands have a payload which is a string of unknown length, but seemingly NUL-terminated. I edited the code, and added an extra EXPECTING_STRING state to handle this special case. It is up to you to implement the boolean function command_payload_is_string().

2
  • Edgar thanks a lot! for your wise answer, after reading 10 times, I have understood half of them :). I have a last question. Almost all command respons lenght are known before. But only reading string is not known before. it deponds on user. You can think like that keyboard input. at this point what should I do, while reading string for packet_length().
    – mehmet
    Jul 30 at 11:56
  • 2
    @mehmet: See amended answer. Now packet_length() will not be called if the payload is of string type. Jul 30 at 13:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.